想要说明闭包,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); }
作者:咸鱼的今生前世
来源:https://segmentfault.com/a/1190000014834135?utm_source=channel-hottest