首先引用MDN给的定义:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
从定义中可以得出,形成闭包有两个必要的条件:
- 有一个函数
- 该函数引用了其周围环境(所在执行上下文,及其上层执行上下文)的变量对象
样例
样例1
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
在函数makeFunc的执行上下文(EC)中有一个活动对象(AO),如下
AO = {
arguments: {
0: undefined,
length: 0
}
name: undefined,
displayName: <displayName reference>, // 表示displayName的地址引用
}
当displayName函数被返回并执行时,会引用makeFunc函数执行上下文中的name变量,导致makeFunc的执行上下文的生命周期一直无法结束,造成闭包。
在大多数理解中,包括许多著名的书籍,文章里都以函数displayName的名字代指这里生成的闭包。而在chrome中,则makeFunc以代指闭包。
样例2
var fn = null;
function foo() {
var a = 2;
function innnerFoo() {
console.log(a);
}
fn = innnerFoo; // 将 innnerFoo的引用,赋值给全局变量中的fn
}
function bar() {
fn(); // 此处的保留的innerFoo的引用
}
foo();
bar(); // 2
innnerFoo函数引用了foo函数执行上下文中的a变量,所以当将innnerFoo函数的引用,赋值给全局变量中的fn,就形成了闭包。当fn在bar函数中执行时,也能够访问到foo函数执行上下文中的a变量。
样例3
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
控制台输出是多少呢?
在弄清楚这段代码前,我们先搞明白setTimeout。
- setTimeout有两个参数,第一个参数为一个函数,我们通过该函数定义将要执行的操作。第二个参数为一个时间毫秒数,表示延迟执行的时间。
- setTimeout函数时等到当前执行上下文中所有可执行代码执行完毕之后,才会开始执行由setTimeout定义的操作
setTimeout(function () {
console.log(a);
}, 0);
var a = 10;
console.log(b);
console.log(fn);
var b = 20;
function fn() {
setTimeout(function () {
console.log('setTImeout 10ms.');
}, 10);
}
fn.toString = function () {
return 30;
}
console.log(fn);
setTimeout(function () {
console.log('setTimeout 20ms.');
}, 20);
fn();
输出结果:
所以我们再重头分析一下开始的setTimeOut代码,当执行环境所有代码执行完成偶,i = 5,然后才开始执行setTimeout的函数,所以最终结果,打印出5个5。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
那如何实现我们最初想要的效果,打印出1,2,3,4,5呢?除了ES6的let,const标识符,我们还是用到上面学到的闭包。利用闭包将外层的变量对象保存下来。
方式1:
for (var i = 1; i <= 5; i++) {
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i)
}
方式2:
for (var i = 1; i <= 5; i++) {
setTimeout((function (i) {
return function () {
console.log(i);
}
})(i), i * 1000);
}