什么是闭包?
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。
举个例子
function foo() {
var a = 2;
function bar() {
console.log( a );//baz()的词法作用域能够访问foo()的内部作用域,即能访问到变量a
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果
当执行完var baz=foo();时,此时的 baz的值即为bar函数,即( function bar() { console.log( a ); } )此时baz函数在自己所定义的词法作用域以外的地方执行,这是一个典型的闭包
此处的return并不是必须的,只是为了外面可以访问到这个 bar 函数。无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时都可以观察到闭包。如
function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); //这也是闭包!
}
把内部函数 baz 传递给 bar,当调用这个内部函数时(现在叫作 fn),它涵盖的 foo() 内部 作用域的闭包就可以观察到了,因为它能够访问 a。
也可以间接的访问传递函数
var fn;
function foo() {
var a = 2;
function baz() {
console.log( a );
}
fn = baz; // 将 baz 分配给全局变量
}
function bar() {
fn(); // 闭包!
}
foo();
bar(); // 2
无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用 域的引用,无论在何处执行这个函数都会使用闭包。
常见的闭包例子
可能你没有意识到,当你已经多次使用过闭包,如下例
function wait(message) {
setTimeout( function timer() {
console.log( message );
}, 1000 );
}
wait( "Hello, closure!" );
以及我们经常使用的jQuery
function setupBot(name, selector) {
$( selector ).click( function activator() {
console.log( "Activating: " + name );
} );
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );
在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使 用了回调函数,实际上就是在使用闭包!
闭包的优缺点
如果我们想达到函数外部能访问内部变量的时候,我们就可以使用闭包,但是闭包会导致变量不会被垃圾回收机制所清除,会大量消耗内存。原因如下
以举的第一个例子为例
function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,由于看上去 foo() 的内容不会再被使用,故引擎的垃圾回收机制会来释放不再使用的内存空间。而闭包的“神奇”之处正是可以阻止这件事情的发生。拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,bar() 本身在使用这个内部作用域,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。
本文章内容参考于《你不知道的JavaScript》