什么是闭包?
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。
下面用一些代码来解释这个定义:
function foo() {
var a = 2;
function bar() {
console.log(a); // 2
}
bar();
}
foo();
这是闭包吗?
技术上来讲,也许是。但根据前面的定义,确切地说并不是。最准确地来解释 bar() 对 a 的应用方法是词法作用域的查找规则(即在 bar() 的函数作用域中无法找到 a,则向上一级所嵌套的 foo() 的作用域中查找),而这些规则只是闭包的一部分。
下面再看一段代码,清晰地展示了闭包:
function foo() {
var a = 2;
function bar() {
console.log(a);
}
return bar;
}
var baz = foo();
baz(); // 2 这就是闭包的效果
函数 bar() 的词法作用域能够访问 foo() 的内部作用域,然后将 bar() 函数本身作为一个值类型进行传递。在这段代码中,我们将 bar 所引用的函数对象本身作为返回值。在 foo() 执行后,其返回值赋值给变量 baz 并调用 baz(),实际上只是通过不同的标识符引用调用了内部函数 bar()。
在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁。而闭包的神奇之处可以阻止作用域被销毁,被回收。那么是谁再使用这个内部作用域?是 bar() 本身在使用。bar() 拥有覆盖 foo() 内部作用域的闭包,使得该作用域能够一直存活,以供 bar() 在之后任何时间进行引用。这个引用就叫做闭包。
再据两个例子:
function foo() {
var a = 2;
function baz() {
console.log(a); //2
}
bar(baz);
}
function bar(fn) {
fn(); // 这就是闭包
}
function wait(message) {
setTimeout(function timer(){
console.log(message);
},1000);
}
wait("Hello World");
循环和闭包
先看下面的例子:
for(var i=1; i<=5; i++){
setTimeout(function timer() {
console.log(i);
},i*1000);
}
这段代码在运行时会以每秒一次的频率输出五次6.为什么会这样呢?
首先解释6是怎么来的。这个循环的终止条件是 i<=5。条件首次成立时 i 的值是6.因此,输出显示的是循环结束时 i 的最终值。
延迟函数的回调会在循环结束时才执行,当定时器运行时即使每个迭代中执行的是 setTimeout(..,0),所有的回调函数依然是在勋魂结束后才会执行,因此每次都输出6.根据作用域的工作原理,实际情况是尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都是被封闭在一个共享的全局作用域中,因此实际上只有一个 i。
再看下一个代码,给上一代码加入更多的词法作用域,且要加入实质内容才能起作用。
for(var i=1; i<=5; i++){
(function() {
var j = i;
setTimeout(function timer(){
console.log(j);
},j*1000)
})();
}
现在就能正常分别输出数字1~5,每秒一次,每次一个。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。