闭包是JavaScript中的重要特性之一,大多数用过JavaScript的程序员也基本上都接触过闭包,不管是否知道或了解闭包这个概念。比如在用jQuery的时候:
var count = 0; $('.btn').onclick = function(e) { count += 1; };
闭包,维基百科的解释是:指引用了自由变量的函数。而我个人认为前端大牛johnhax的解释更加容易理解:闭包就是内部函数能访问外部的变量。
所以,要理解闭包,只要理清楚变量作用域这个概念就差不多了。我也把对变量作用域的一些个人理解记录在了前两篇文章中,故这里就只简单说说一个函数它可以访问哪些作用域中的变量:
- 该函数自己内部声明的变量
- global作用域中的全局变量
- 如果该函数是内部函数,那它还可以访问其外部函数内声明的变量
而对于第三点,就是闭包的行为了,用一个简单的例子来说明。
function foo() { var i = 0; function bar() { i += 1; console.log(i); } return bar; } var test = foo(); test(); // 1 test(); // 2
简单解读下这段代码:
bar
函数是内部函数,即定义在foo
函数内部foo
函数调用之后会将bar
函数的引用作为返回值- 全局作用域中有变量引用了
bar
函数,即bar
函数还处于活动状态
于是在foo
函数已经被调用结束之后,其内部的i
变量仍然没被销毁,而且在每次调用bar
函数之后,其值是一直在递增的。
如果用作用域链来解释,那就更加清晰明了了:
1.代码载入全局执行环境,开始运行。
globalScopeChain = {
test: undefined,
foo: [Function]
};
fooScopeChain = [globalScopeChain];
2.调用foo
函数。
// 首先,`foo`函数的作用域链变为:
fooScopeChain = [{ arguments: [], i: 0, bar: [Function] }, { test: undefined, foo: [Function] }];
// 声明了`bar`函数,并初始化其作用域链为: barScopeChain = [fooScopeChain];
3.foo
函数调用结束,并将test
变量指向foo
执行返回的bar
函数的引用。
// 此时全局作用域链变为:
globalScopeChain = [{
test: [Function],
foo: [Function]
}];
4.第一次调用test
,也就是bar
函数。bar
函数作用域链上有变量i
,且值为0
。
// 作用域上进行变量查找,找到后做增量赋值操作,所以`i = 1`
barScopeChain = [{ arguments: [] }, { i: 0, bar: [Function] }, { test: undefined, foo: [Function] }];
5.第二次调用test
,与第4步相同。只是本次调用时,其作用域链上的i
值已经在上一次调用后变成1
了,所以增量赋值操作之后,得到结果为2
。
因此,只要理解了作用域链,再来看闭包就很清晰了。不过对于闭包的解释,我仍然推荐开篇所提到的:内部函数能访问外部的变量,简单易懂。