什么是闭包

有权访问另一个函数作用域中的变量的函数,就是闭包。

这是一种闭包

1
2
3
4
5
6
7
8
function animal() {
const name = "dog";
function getName() {
console.log(name);
}
getName();
}
animal();

这也是一种闭包

1
2
3
4
const name = "cat";
function getName() {
console.log(name);
}

name 定义在全局作用域中,getName 在内部输出找不到自己定义的值,因此向外寻找,输出 getName,是一个隐性的闭包。

一个例子记住

函数在创建时,会保存所有父变量对象到其中的[scope]中。函数激活时,会将自身的活动对象添加到作用链的前端

1
2
3
4
5
6
7
8
9
10
11
12
var data = [];

for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
// data中的每个元素的scope均为[global.vo]

data[0]();
data[1]();
data[2]();

输出均为3

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

1
2
3
4
5
6
globalContext = {
VO: {
data: [...],
i: 3
}
}

当执行 data[0] 函数的时候,data[0] 函数的作用域链为:

1
2
3
data[0]Context = {
Scope: [AO, globalContext.VO]
}

data[0]Context 的 AO 并没有 i 值,所以会从 globalContext.VO 中查找,i 为 3,所以打印的结果就是 3。

data[1] 和 data[2] 是一样的道理。

所以让我们改成闭包看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var data = [];
// 此处需注意 需要将i传递给匿名函数 如果不进行传递 匿名函数的ao中就不会有i这个变量 最后就会去全局变量对象中寻找i
for (var i = 0; i < 3; i++) {
data[i] = (function (i) {
return function(){
console.log(i);
}
})(i);
}
// data中的每个元素的scope均为[匿名函数.ao,global.vo]

data[0]();
data[1]();
data[2]();

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

1
2
3
4
5
6
globalContext = {
VO: {
data: [...],
i: 3
}
}

跟没改之前一模一样。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

1
2
3
data[0]Context = {
Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

匿名函数执行上下文的AO为:

1

data[0]Context 的 AO 并没有 i 值,所以会沿着作用域链从匿名函数 Context.AO 中查找,这时候就会找 i 为 0,找到了就不会往 globalContext.VO 中查找了,即使 globalContext.VO 也有 i 的值(值为3),所以打印的结果就是0。

data[1] 和 data[2] 是一样的道理。

文章参考https://github.com/mqyqingfeng/Blog/issues/9,对其进行了注释方便学习