-
闭包的缺点和解决
闭包会使函数中的变量都放在内存中,对内存消耗很大,会造成内存泄漏或内存溢出,所以在退出函数之前要手动释放闭包,释放闭包后就相当于删除了函数中的变量,释放闭包后再调用函数,就没有变量可引用了,就会报错
function f1(){ var a = 996; function f2(){ a++; console.log(a); } return f2; } var result = f1(); result(); result(); result = null;//释放闭包,局部变量自动销毁 result();//局部变量销毁了,无效了报错了
5.Javascript预解析
- 当浏览器解析到script标签的时候,会立即扫描script标签中的所有代码,然后进行预解析
- 预解析包含变量提升和函数提升
- 预解析,解析的是带var的变量和由function定义的函数
1.变量提升
-
带var的变量提升
console.log(a); var a = 1; console.log(a); //预解析结果 var a; //声明,未赋值 console.log(a); //所以 返回undefined a = 1; //变量声明提升了,赋值了 console.log(a); //返回1
-
不带var的变量不会提升
console.log(a); var a = 1; console.log(a);
2.函数提升
f1()
function f1(){
console.log('奥利给');
}
// 解析结果
function f1(){
console.log('奥利给');
}
f1()
3.特点
- 带var的变量和的带function的函数都会被预解析
- 函数体内的代码会发生预解析
- 函数的预解析的优先级高于变量预解析的优先级
- 函数名一样会覆盖
- 函数名和变量名一样会忽略变量
- 如果是表达式定义的函数,只会提升变量不会提升函数体
- 解析三步走:第一步先解析函数,(函数内有代码则左局部解析),第二步:解析变量,第三步:按顺序执行
4.练习
// 不会预解析,变量前面没有var
alert(a);
a = 0;
alert(a);
var a = 0;
alert(a);
// 提升结果
var a;
alert(a);
a = 0;
alert(a);
// 输出undefined和0
console.log(a);
var a = '我是变量';
function a() {
console.log('我是函数')
}
console.log(a);
// 提升结果
// 函数提升的优先级要高于变量提升的优先级
// 变量重名会忽略 >>> 函数名和变量名相同时,忽略变量名
function a(){
console.log('我是函数')
}
var a; //(此时的a是函数名a,因为跟变量名重复了,忽略了变量名)
console.log(a); //输出函数体(因为a没有加括号,就是函数体)
a = '我是变量';
console.log(a); //输出我是变量
console.log(a);
a++;
console.log(a);
var a = '我是变量';
function a() {
console.log('我是函数')
}
console.log(a)
// 提升结果
// 优先提升函数
function a(){
console.log('我是函数')
}
var a; //(此时的a是函数名a,同名忽略变量名)
console.log(a); //输出函数体
a++;
console.log(a); //输出NaN(函数体++输出肯定是NaN啊)
a = '我是变量';
console.log(a); //输出我是变量
console.log(a);
var a = 0;
console.log(a);
function fn() {
console.log(a);
var a = 1;
console.log(a);
}
fn()
console.log(a);
// 解析结果
// 函数内部也要做局部变量提升
function fn(){
var a; //函数内部变量提升
console.log(a); //输出undefined
a = 1;
console.log(a); //输出1
}
var a;
console.log(a); //输出undefined
a = 0;
console.log(a); //输出0
fn() //输出函数内的undefined和1
console.log(a); //上一级的函数执行完了就自动销毁局部变量了,所以a的值要继续向上找(作用域链),输出0
console.log(a);
var a = 0;
console.log(a);
function fn() {
console.log(a);
a = 1;
console.log(a);
}
fn()
console.log(a);
// 解析结果
function fn() {
// 函数内部没有带var的变量,不做预解析
console.log(a); //输出0(向上一级找变量,函数内没有变量提升)
a = 1;
console.log(a); //输出1
}
var a;
console.log(a); //输出undefined
a = 0;
console.log(a); //输出0
fn() //输出函数内的undefined和1
console.log(a); //向上一级找变量(作用域链),因为函数内的是全局变量,执行完后不自动销毁,找到a = 1就引用,输出1
// 函数名重复会覆盖
f1();
function f1(){
console.log(11111);
}
function f1(){
console.log(22222);
}
// 函数名和变量名重复
var a = 1;
function a(){
}
console.log(a);
//解析结果
// 并没有忽略变量,因为var a = 1在console.log前面
function a(){
}
var a;
a = 1;
console.log(a);
console.log(a);
var a = 1;
function a(){
}
// 解析结果
// 此时变量名和函数名重复,先提升函数,再提升变量,输出a,就忽略了a = 1;
function a(){
}
var a;
console.log(a);
a = 1;
6.Javascript函数类型
1.具名函数
有名字的函数
function f1(){
}
2.匿名函数
没有名字的函数
var f1 = function (){
}
3.立即执行函数IIFE
1.书写方法
第一步,创建一个匿名函数
function(){
console.log('谁在让我东张西望');
}
第二步,报错,说必须有一个函数名,解决方法给函数加一个圆括号
(function(){
console.log('谁在让我东张西望');
})
第三步,然后在函数后面加一个空的圆括号
(function(){
console.log('谁在让我东张西望');
})()
- 其他IIFE的格式
前面加叹号,后面加圆括号
!function(){
console.log('谁是你的新郎')
}();
前面加波浪号号,后面加圆括号
~function(){
console.log('谁是你的新娘');
}();
前面加加号,后面加圆括号
+function(){
console.log('哎嘿嘿');
}();
前面加减号,后面加圆括号
-function(){
console.log('你快快来到我的身旁');
}();
2.IIFE的参数
// 参数的传递
+function(name){
console.log('大家好我是' + name);
}('陈冠希'); //这是实参
3.特点
1.匿名函数自调用是在定义的时候同时执行函数
2.匿名函数只能执行一次
如果一个函数可以多次执行,那这个函数必须是具名函数
如果函数没有名字要想执行必须是自调用,但是只能执行一次
3.匿名函数自调用,函数整体不会发生预解析,函数内部还是要预解析的
4.作用
1.防止外部命名空间污染
- 一个页面中会有很多重复的变量的名字,那么命名就冲突了,所以用匿名函数 自调用来隔离不同业务得同名变量
2.隐藏内部接口,避免暴露代码
- 很多js文件只有引入路径,引入之后就可以直接用,看不到代码细节就保证了文件的安全
3.对项目初始化,只执行一次
- 因为立即执行函数只执行一次,对于项目加载问价是很友好的,省略了很多加载时间
7.Javascript函数参数的高级
1.获取参数的个数
-
获取函数实参的个数 >>> arguments对象只能在函数内可见,所以arguments.length只能在函数体内使用
-
函数对象的length是获取函数形参的个数 >>> 该属性为只读属性,在函数体内外都可以使用
// 函数对象的length是获取函数形参的个数 console.log(f1.length); function f1(a,b,c,d,e,f,g){ // 获取函数实参的个数 console.log(arguments.length); } f1(1,2,3,4,5,6)
2.使用arguments对象
-
argumentss 是一个实参的数组
-
这个数组是一个伪数组(不能调用数组的方法,但是可以通过下标来访问数组的元素)
-
// 通过修改length属性值,可以改变函数的实参个数。
function f1(a,b,c,d){ var aList = arguments; console.log(arguments[1]); arguments.length = 100; // 打印这数组,后面加了97个空 console.log(arguments); // 打印数组的长度(100) console.log(arguments.length); } f1(1,2,3);
案例1:使用arguments计算你传递过来的2个参数的和
function f1(){
return arguments[0] + arguments[1];
}
console.log(f1(1,3));
案例2:输入一组数字,求平均值的函数
无论输入的是多少个数字 那么我就要平均值。
function f2(){
var sum = 0;
var len = arguments.length;
for(i = 0; i < len; i++){
sum += arguments[i]
}
return sum / len;
}
console.log(f2(1,2,3,4,5,6,7,8,9,10));
案例3:如果传递过来的参数是2个话 那么就计算减法 如果传递过来的参数是3个参数 那么就计算加法 并且返回
function f3(){
var len = arguments.length;
if(len == 2){
return arguments[0] - arguments[1];
}else if(len == 3){
return arguments[0] + arguments[1] + arguments[2];
/* var sum = 0;
for(i = 0; i < len; i++){
sum += arguments[i];
}
return sum; */
}
}
console.log(f3(2,3,6));
案例4:书写一个函数 计算 三个值的和
function f4(a,b,c){
return a + b + c;
}
console.log(f4(11,22,33));
但是这里如果只传了两个实参,c就是undefined了,undefined转Number就NaN了,因为c没有参数,在求和的时候直接成NaN了
解决办法,给c设一个默认值=0,传两个的时候就是和0相加了
function f5(a,b,c = 0){
console.log(typeof c);
return a + b + c;
}
console.log(f5(11,22));
问题又出现了,当我不确定要不要传第三个参数的时候,c的默认值要不要,成了个问题
解决办法,在传了参数的时候,让c用传了了参数,当没传参的时候,让c默认为0
function f6(a,b,c){
typeof c == "undefined" ? c = 0 : c
// (c的数据类型)(如果等于)("undefined") (那么c赋值为0)(否则)(c还是c)
return a + b + c;
}
console.log(f6(11,22,33));