什么是闭包
闭包是可以读取其他函数内部变量的函数
当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包
下面我们可以通过一段代码,清晰地展示了闭包:
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 这就是闭包的效果
函数 bar() 的词法作用域能够访问 foo() 的内部作用域,我们将 bar 所引用的函数对象本身当作返回值,在 foo() 执行后,将其返回值赋值给变量 baz 并调用 baz(),实际只是通过不同的标识符引用调用了内部的函数 bar()。此时,bar() 在自己定义的词法作用域以外的地方执行。
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃圾回收器用来释放不再使用的内存空间,由于看上去 foo() 的内容不会再被使用,所以很自然地会考虑对其进行回收。
而闭包的“神奇”之处正是可以阻止这种事情的发生。事实上内部作用域依然存在,因此没有被回收。
拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够这一直存活,以供 bar() 在之后任何时间进行引用。
bar() 依然持有对该作用域的引用,而这个引用就叫做闭包
这个函数在定义时的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。
无论通过何种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包
function foo() {
var a = 2;
function baz() {
console.log(a);
}
bar(baz);
}
function bar(fn) {
fn(); // 闭包
}
foo();
是不是看着有点死板,但其实这就是我们常用的一种闭包形式
function wait(message) {
setTimeout(function timer() {
console.log(message);
}, 1000)
}
wait('hello');
内置的工具函数 setTimeout 持有对一个参数的引用,这个参数也许叫做 fn 或者 func,或者其他类似的名字。引擎会调用这个函数,在例子中就是内部的 timer 函数,而词法作用域在这个过程中保持完整。这就是闭包。
本质上,无论何时何地,如果将函数当作第一级的值类型到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、ajax请求或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包。
模块
还有其他的代码模式利用了闭包的强大威力,模块就是其中最强的一个。
function CoolModule() {
var something = 'cool';
var another = [1, 2, 3];
function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join());
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1,2,3
模块模式需要具备的两个必要条件:
- 必须有外部的封闭函数。该函数必须至少被调用一次;
- 封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
最后
虽然闭包非常有用,但是不能过度使用。使用闭包时,所有的信息都会存储在内存中,直到 JavaScript 引擎确保这些信息不再使用或页面卸载时,才会清理这些信息。