什么是闭包?
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。
下面用一些代码来解释这个定义:
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,每秒一次,每次一个。