什么是闭包
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。参照阮一峰的博客的理解是,闭包就是能够读取其他函数内部变量的函数。
当一个函数能够记住并访问到其所在的词法作用域及作用域链,特别强调是在其定义的作用域外进行的访问,此时该函数和其上层执行上下文共同构成闭包。
需要明确的几点:
- 闭包一定是函数对象
- 闭包和词法作用域,作用域链,垃圾回收机制息息相关
- 函数一定是在其定义的作用域外进行的访问时,才产生闭包
- 闭包是由该函数和其上层执行上下文共同构成
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
产生一个闭包
创建闭包最常见方式,就是在一个函数内部创建另一个函数。 下面例子中的 closure 就是一个闭包:
function func(){
var a = 1,b = 2;
function closure(){
return a+b;
}
return closure;
}
闭包的用途
- 可以读取函数内部的变量
- 让这些变量的值始终保持在内存中。
来看下阮一峰博客上的例子:
function f1() {
var n = 999;
nAdd = function () {
n += 1
}
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
闭包的注意事项
-
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
-
闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
有关闭包的面试题
for( var i = 0; i < 5; i++ ) {
setTimeout(() => {
console.log( i );
}, 1000 * i)
}
由于setTimeout中的回调函数会在当前任务队列的尾部进行执行,因此上面例子中每次循环中的setTimeout回调函数记住的i的值是for循环作用域中的值,此时都是5
答案:每秒钟输出一个5,一共输出5次。

那么如何做到每秒钟输出一个数,以此为0,1,2,3,4呢?这里介绍下闭包的解决方法
for( var i = 0; i < 5; i++ ) {
((j) => {
setTimeout(() => {
console.log( j );
}, 1000 * j)
})(i)
}
"setTimeout"方法里应用了闭包,使其内部能够记住每次循环所在的词法作用域和作用域链。记住的i的数为setTimeout的父级作用域自执行函数中的j的值,依次为0,1,2,3,4。
