function foo()
{
var a = 2;
function bar()
{
console.log(a);
}
return bar;
}
var baz = foo();
baz(); //2
这段代码清晰地展示了闭包
函数bar()的作用域能够访问 foo()的内部作用域,然后将bar()函数本身当做一个值类型进行传递,函数foo()的返回值也就是内部的bar()函数。在执行foo()赋值给变量baz后,并调用了baz(),实际上只是通过不同的标识符调用了内部的函数bar()。
闭包可以阻止被回收,bar()拥有涵盖foo()内部作用域的闭包,使得该作用域能够一直存活,以方便bar()(也可以是变量baz)在之后任何时间进行引用。
这个函数在定义时的词法作用域以外被调用。闭包使得函数可以继续访问定义时的词法作用域。
要说明闭包,for循环是最常见的例子
for (var i = 1; i <=5; i++) {
setTimeout(function time(){
alert(i);
},i*1000);
}
这段代码会每隔1秒弹出6,一共弹出5次
为什么不是每隔1秒弹出12345,而是循环终止结束时的i值6。
弹出12345的话,是因为我们觉得循环中的每个迭代在运行时都会给自己捕获一个i的副本。
但是根据作用域和闭包的原理,实际是虽然循环中的五个函数是在各个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域中,因此实际上只有一个i,所有函数共享一个i的引用。
想要输出1,2,3,4,5的话,循环过程中的每个迭代都需要一个闭包作用域。
可以通过两个方法解决这个问题:
1.通过立即执行函数
for (var i = 1; i <=5; i++) {
(function() {
var j=i;
setTimeout(function time(){
alert(j);
},j*1000);
})();
}
立即执行函数会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部。
2.ES6中的let声明
let声明的变量有块级作用域,只能在作用域中访问,其他和var一样。
for (let i = 1; i <=5; i++)
{
setTimeout(function time()
{
alert(i);
},i*1000);
}