JavaScript闭包

闭包是JavaScript中的重要特性之一,大多数用过JavaScript的程序员也基本上都接触过闭包,不管是否知道或了解闭包这个概念。比如在用jQuery的时候:

1
2
3
4
var count = 0;
$('.btn').onclick = function(e) {
count += 1;
};

闭包,维基百科的解释是:指引用了自由变量的函数。而我个人认为前端大牛johnhax的解释更加容易理解:闭包就是内部函数能访问外部的变量。

所以,要理解闭包,只要理清楚变量作用域这个概念就差不多了。我也把对变量作用域的一些个人理解记录在了前两篇文章中,故这里就只简单说说一个函数它可以访问哪些作用域中的变量:

  1. 该函数自己内部声明的变量
  2. global作用域中的全局变量
  3. 如果该函数是内部函数,那它还可以访问其外部函数内声明的变量

而对于第三点,就是闭包的行为了,用一个简单的例子来说明。

1
2
3
4
5
6
7
8
9
10
11
12
function foo() {
var i = 0;
function bar() {
i += 1;
console.log(i);
}
return bar;
}

var test = foo();
test(); // 1
test(); // 2

简单解读下这段代码:

  1. bar函数是内部函数,即定义在foo函数内部
  2. foo函数调用之后会将bar函数的引用作为返回值
  3. 全局作用域中有变量引用了bar函数,即bar函数还处于活动状态

于是在foo函数已经被调用结束之后,其内部的i变量仍然没被销毁,而且在每次调用bar函数之后,其值是一直在递增的。

如果用作用域链来解释,那就更加清晰明了了:

1.代码载入全局执行环境,开始运行。

1
2
3
4
5
6
globalScopeChain = {
test: undefined,
foo: [Function]
};

fooScopeChain = [globalScopeChain];

2.调用foo函数。

1
2
3
4
5
6
7
8
9
10
11
12
// 首先,`foo`函数的作用域链变为:
fooScopeChain = [{
arguments: [],
i: 0,
bar: [Function]
}, {
test: undefined,
foo: [Function]
}];

// 声明了`bar`函数,并初始化其作用域链为:
barScopeChain = [fooScopeChain];

3.foo函数调用结束,并将test变量指向foo执行返回的bar函数的引用。

1
2
3
4
5
// 此时全局作用域链变为:
globalScopeChain = [{
test: [Function],
foo: [Function]
}];

4.第一次调用test,也就是bar函数。bar函数作用域链上有变量i,且值为0

1
2
3
4
5
6
7
8
9
10
// 作用域上进行变量查找,找到后做增量赋值操作,所以`i = 1`
barScopeChain = [{
arguments: []
}, {
i: 0,
bar: [Function]
}, {
test: undefined,
foo: [Function]
}];

5.第二次调用test,与第4步相同。只是本次调用时,其作用域链上的i值已经在上一次调用后变成1了,所以增量赋值操作之后,得到结果为2

因此,只要理解了作用域链,再来看闭包就很清晰了。不过对于闭包的解释,我仍然推荐开篇所提到的:内部函数能访问外部的变量,简单易懂。