理解闭包之前需要先知道以下两个知识点:
1.JavaScript中的作用域和作用域链
- 作用域就是一个独立的‘地盘’,让变量不会外泄、暴露出去,不同作用域下同名变量不会有冲突。
- 作用于在定义时就确定,并且不会改变
- 如果在当前作用域中没有查到值,就会向上级作用域去查,知道查到全局作用域,这个查询过程形成的链条就叫做作用域链
2.Javascript中的垃圾回收
- Javascript 执行环境会负责管理代码执行过程中使用的内存,其中就涉及到一个垃圾回收机制。
- 垃圾收集器会定期(周期性)找出那些不再继续使用的变量,只要该变量不再使用了,就会被垃圾收集器回收,然后释放其内存。如果该变量还在使用,那么就不会被回收。
什么是闭包
闭包是一种现象,是在定义函数时,周围环境中的信息可以在函数中使用。换句话说,执行函数时,只要在函数中使用了外部的数据,就创建了闭包。
而作用域,正是实现闭包的手段。
第一种情况

可以打印出结果,但是16行会报错,因为自动形成的闭包,会被销毁。
第二种情况
function eat(){
var food = '鸡翅';
return function(){
console.log(food);
}
}
var look = eat();
look(); // 鸡翅
look(); // 鸡翅
为什么能访问到food?
上面的示例中,照理说 eat 调用完毕 food 就应该被销毁掉,但是我们向外部返回了 eat 内部的匿名函数,匿名函数中引用了 food,所以垃圾回收器不会对其进行回收,所以在外面调用这个匿名函数时,仍然能够打印出food的值。
垃圾回收器只会回收没有被引用到的变量,但是一旦一个变量还被引用着的,垃圾回收器就不会回收此变量。
至此,闭包的优点也就体现出来了:
- 通过闭包可以让外部环境访问到函数内部的局部变量。
- 通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。
经典问题
for (var i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 4 4 4
循环中setTimeout访问了它的外部变量,形成闭包。三个timeout访问的是同一个变量。一秒后,i已经为4。
解决:
1.将i作为立即执行函数的参数传递,访问自己作用域内变量,不创建闭包。
for (var i = 1; i <= 3; i++) {
(function (index) {
setTimeout(function () {
console.log(index);
}, 1000);
})(i)
}
// 1 2 3
2.利用es6语法,生成块级作用域,每次循环都有一个新的i,即使有闭包也没问题,因为保存的是不同的i。
for (let i = 1; i <= 3; i++) {
setTimeout(function () {
console.log(i);
}, 1000);
}
// 1 2 3
闭包的面试题
闭包是什么?闭包的应用场景有哪些?怎么销毁闭包?
- 闭包是一个封闭的空间,里面存储了在其他地方会引用到的该作用域的值,在 JavaScript 中是通过作用域链来实现的闭包。
- 只要在函数中使用了外部的数据,就创建了闭包,这种情况下所创建的闭包,我们在编码时是不需要去关心的。
- 我们还可以通过一些手段手动创建闭包,从而让外部环境访问到函数内部的局部变量,让局部变量持续保存下来,不随着它的上下文环境一起销毁。
场景:使用闭包可以解决一个全局变量污染的问题。
销毁:如果是自动产生的闭包,我们无需操心闭包的销毁,而如果是手动创建的闭包,可以把被引用的变量设置为 null,即手动清除变量,这样下次 JavaScript 垃圾回收器在进行垃圾回收时,发现此变量已经没有任何引用了,就会把设为 null 的量给回收了。
对闭包不同的定义
(助力于更好理解闭包)
MDN的定义
闭包是指那些能够访问自由变量的函数。
那么什么是自由变量呢?
指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量。
JavaScript权威指南
从技术的角度讲,所有的JavaScript函数都是闭包
ECMAScript
1.从理论角度:
所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。
2.从实践角度:以下函数才算是闭包:
1.即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
2.在代码中引用了自由变量