闭包的定义

○ 从概念上讲,闭包是“包含上层作用域变量的函数”与“函数相关引用环境”两者组成的实体。其中的“函数相关引用环境”就是函数的词法环境链,是所有函数被执行前都会被创建的。因此在广义上只要函数内部引用了外部变量就构成闭包。词法环境相关内容整理在这篇
○ 经常讨论的闭包出现在“局部作用域中内嵌函数”的场景,且同时满足两个要点:
(1)内嵌函数引用了上层作用域的变量
(2)内嵌函数被传递到所在作用域的外面
○ 对于函数作用域构成的局部作用域来说,也就是函数内嵌函数的场景,当内嵌函数引用了外部函数作用域内的变量,内嵌函数又通过 return 或赋值方式传递到外面,就可以达到“外部函数执行结束后仍可访问外部函数作用域内变量”的效果,如此就构成了一个闭包。
○ 在块级作用域中的内嵌函数,使用同样思路也可以构成闭包,如下例:

1
2
3
4
5
6
7
8
9
10
var things = {};
for (let i = 0; i < 3; i++) {
things['fun' + i] = function() {
console.log(i);
};
}

things['fun0'](); // 0
things['fun1'](); // 1
things['fun2'](); // 2

☆解析:根据MDN的说明,for括号中使用var声明的变量存在于for循环体外面,而使用let声明的变量存在于for循环体之内的块级作用域。

闭包的用途

◆ 从外部访问局部作用域内的内容。
◆ 将局部内容保持在内存中。

理解实例

实例 1
1
2
3
4
5
6
7
8
function f1(){
var f1v = 2;
return function(){
return f1v;
}
}
var getF1vFun = f1();
console.log(getF1vFun()); // 打印f1v的值2

☆说明:“函数内嵌函数”的经典场景。当函数f1执行完毕,其中被闭包引用的局部变量f1v不会被垃圾回收机制回收。

实例 2
1
2
3
4
5
6
7
8
var oneArr=[1,2,3];
for(var i=0;i<oneArr.length;i++)
{
setTimeout(function(){
var oneElem= oneArr[i];
console.log(oneElem);
}, 1000*i); // 3秒内每隔1秒打印一次undefined
}

☆解析:传入setTimeout的函数中引用了外部变量i构成广义闭包,但var声明的i变量存在于循环体外面的全局作用域,闭包中保留的i也是全局作用域中的变量i,setTimeout的回调函数执行的时候,i值已经是for循环执行完毕后的值3,将打印三次oneArr[3]。

实例 3
1
2
3
4
5
6
7
8
9
10
11
var oneArr=[1,2,3];
for(var i=0;i<oneArr.length;i++)
{
setTimeout((function(index){
return function(){
var oneElem= oneArr[index];
console.log(oneElem);
};
})(i), 1000*i); // 3秒内每隔1秒依次打印1,2,3
}

☆解析:在为setTimeout传入回调函数的位置,通过构造“函数内嵌函数的闭包”将局部作用域参数index保持在了内存中。

实例 4
1
2
3
4
5
6
7
8
var oneArr=[1,2,3];
for(let i=0;i<oneArr.length;i++)
{
setTimeout(function(){
var oneElem= oneArr[i];
console.log(oneElem);
}, 1000*i); // 3秒内每隔1秒依次打印1,2,3
}

☆解析:“块级作用域内嵌函数的闭包”场景,内嵌函数引用局部作用域的变量i,又将内嵌函数以传入setTimeout注册回调函数的方式传出。