闭包可以看做是把函数的词法作用域保存下来,以供在其他外部调用时能够做到访问权限的控制。参考如下代码:
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
bar()被调用时访问的是baz()的作用域,无法访问到baz()定义外的作用域。
var fn;
function foo() {
var a = 2;
function baz() {
console.log( b ); // reference error, b不在baz()的作用域中
console.log( a );
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
var b = 3;
fn(); // 妈妈快看呀,这就是闭包!
}
foo();
bar(); // 2
要说明闭包,for 循环是最常见的例子。
思考
分别输出数字 1~5,每秒一次,每次一个。
for (var i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
实际上以上代码会以每秒一次的频率输出五次 6 。
首先解释 6 是从哪里来的。这个循环的终止条件是 i 不再 <=5。条件首次成立时 i 的值是 6 。因此,输出显示的是循环结束时 i 的最终值。
导致这样输出的原因是所有的函数在寻找 i 时共享同一个 i ,因此我们需要闭包来分隔 i 。
函数可以创建自己的作用域,因此我们可以使用函数来分隔作用域。
for (var i=1; i<=5; i++) {
(function() {
var j = i;
setTimeout( function timer() {
console.log( j );
}, j*1000 );
})();
}
使用let 声明解决上面问题。for 循环头部的 let 声明还会有一个特殊的行为。这个行为指出变量在循环过程中不止被声明一次,每次迭代都会声明。随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
for (let i=1; i<=5; i++) {
setTimeout( function timer() {
console.log( i );
}, i*1000 );
}
总结
本质上无论何时何地,如果将函数(访问它们各自的词法作用域)当作第一级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!如果熟悉Java的话可以感觉到闭包和Java的类差不多。
参考文献:
《你不知道的JavaScript上卷》
版权声明:本文为kongbaifeng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。