【JS】闭包是怎么形成的,有什么作用,经典面试题

241 阅读4分钟

经典面试题

先看两道经典面试题:

练习题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"

画图解题更加形象,更加清晰,于是亲手画了一张图来解决这道题:

(画的好丑)

这种题如果不是非常熟练的话,建议画图来解决

遇到这部分的题都建议画图来解决,凭空想象容易出错,会有遗漏

此题中用到了我前文中写过的知识点,如有看不懂的地方,可以去看我前面发过的博客

如有错误,欢迎指正,共同进步~