想要说明闭包,for循环是最常见的例子:
for(var i=1;i<=5;i++)
{
setTimeout(function timer(){
console.log(i);
},i*1000);
}
以我们所想,我们可能认为他会输出1~5,每秒一次,每次一个。
但实际上,这段代码在运行时会以每秒一次的频率输出五次6。
这是为什么?
原因是延迟函数会在循环结束时才执行,事实上,当定时器运行时即使每个迭代中执行的是setTimeout(...,0),所有的回调函数依然是在循环结束后才会执行,因此会每次输出一个6出来。
根据作用域的原理,实际情况:尽管循环中的五个函数是在各个迭代中分别定义的,但是他们都被封闭在一个共享的全局作用域中,因此实际上只有一个i。
所以所有函数共享一个i的引用时,循环结构让我们误认为背后还有更复杂的机制在器作用,但实际上啥都木有,如果将延迟函数的回调重复定义五次,完全不使用循环,那他同这段代码是完全等价的。
解决方法如下:
我们先试一下:
for(var i=1;i<5;i++){
(function(){
setTimeout(function timer(){
console.log(i);
},i*1000);
})();
}
看似可以,但实际也没用,虽然这样写我们有更多词法作用域了,的确每个延迟函数都会将IIFE在每次迭代中创建的作用域封闭起来。
如果作用域是空的,那么仅仅将他们进行封闭是不够的。仔细看一下,我们的IIFE只是一个什么都没有的空作用域,所以需要包含一点实际内容为我们所用。
他需要自己的变量,用来在每个迭代中存储i的值:
for(var i=0;i<=5;i++)
{
(function(){
var j=i;
setTimeout(function timer(){
console.log(j);
},j*1000);
})();
}
ok,他运行如我们所愿了!
可以进行改进:
for(var i=1;i<=5;i++)
{
(function{
setTimeout(function timer(){
console.log(j);
},j*1000);
})(i); //i可以改动,只要你喜欢
}
在迭代内使用IIFE会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会包含一个具有正确值的变量供我们访问。
使用let解决
for循环的let声明还会有一个特殊行为,这个行为之处变量在循环过程中不知被声明一次,每次迭代都会声明,随后的每个迭代都会使用上一个迭代结束时的值来初始化这个变量。
for(var i=1;i<=5;i++)
{
let j=i; //闭包
setTimeout(function timer(){
console.log(j);
},j*1000);
}
下面是进化版
for(let i;i<=5;i++)
{
setTimeout(function timer(){
console.log(i);
},i*1000);
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。