经典面试题
先看两道经典面试题:
练习题1:
let x = 1;
function A(y){
let x = 2;
function B(z){
console.log(x+y+z);
}
return B;
}
let C = A(2);
C(3);
答案: 7
练习题2:
let x =5;
function fn(x){
return function(y){
console.log(y+(++x));
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
答案: 14 18 18 5
用到的知识点:作用域不销毁 闭包 作用域链 函数执行机制
作用域不销毁的条件
函数执行时会形成私有作用域,供代码执行的环境,也就是我们之前提到的EC(fn)。函数执行完以后作用域不销毁,也就是进栈以后不出栈,需要两个条件:
1、return 返回了一个引用数据类型,如对象或者函数
2、返回的这个引用数据类型被外界所接收,也就是被函数外面的变量或者对象的属性名等接收,占用,则作用域不销毁
前面的两道练习题都用到了这部分知识
闭包
闭包就是一个机制,主要有两个作用:
- 1、保护:形成了私有作用域,保护里面的私有变量和外界互不干扰
- 2、保存:如果私有作用域里面的东西被外界占用了,形成了不销毁的私有作用域,把里面的私有变量保存下来
闭包是怎么形成的
函数执行形成了不销毁的私有作用域,里面的变量、函数、对象等被保存了下来,这就形成了我们所谓的闭包。这种说法,是被目前大众广泛接受的一种说法。当然也有其他的说法,此处就不列举了。
闭包的作用
闭包有 保护 和 保存 的作用
- 保护:可以保护私有作用域中的私有变量不受外界变量的干扰,不会被污染、覆盖,即使外界有与私有变量重名的变量,也不会干扰到闭包内部的私有变量。
- 保存:闭包中的私有变量在函数执行完以后,会被保存下来,存储的值就是函数执行完的时候存储的值,当外界调用了闭包中返回的引用数据类型值时,如果用到了闭包中的私有变量,则仍然可以通过作用域链的机制找到闭包中被保存下来的私有变量。
闭包的两面性
闭包有优点也有缺点,优点就是上面提到的保护和保存的作用,可以减少很多不必要的代码,缺点就是由于作用域不销毁,进栈以后不出栈,导致这部分内存一直被占用,如果闭包非常多,会很消耗性能。闭包的缺点就是会影响性能,在必要的情况下再去使用闭包,如果可以用其他简单的方法解决的话,就使用其他的方法,比如常用到自定义属性来解决一些类似的问题,因为自定义属性也可以起到保存的作用,相当于把内容挂载到对象上面,作为一个键值对存储起来。
易错点
注意: 函数只要执行,就会形成全新的私有作用域 EC(fn) ,如果用到了不是本私有作用域中的私有变量的变量,则会通过作用域链机制向上级作用域查找,直到找到这个变量为止,如果到全局作用域还是没有找到这个变量,就会报错,报引用错误。
举几个例子:
(function(){
return x
})()
console.log(x);
=> 报错 Uncaught ReferenceError: x is not defined
(function(){
return ++x
})()
console.log(x);
=> 报错 Uncaught ReferenceError: x is not defined
(function(){
x = 1;
return ++x
})()
console.log(x);
=> 2
如果类似于上述函数中 x =1 这样给未声明的变量赋值,通过作用域链向上查找,找到全局作用域的时候,会给window增加一个叫做 x 的属性,属性值就是 1
做个练习
let a=0,
b=0;
function A(a){
A=function(b){
alert(a+b++);
};
alert(a++);
}
A(1);
A(2);
答案: "1" "4"
画图解题更加形象,更加清晰,于是亲手画了一张图来解决这道题:
(画的好丑)
这种题如果不是非常熟练的话,建议画图来解决
遇到这部分的题都建议画图来解决,凭空想象容易出错,会有遗漏
此题中用到了我前文中写过的知识点,如有看不懂的地方,可以去看我前面发过的博客
如有错误,欢迎指正,共同进步~