想要说明闭包,for循环是最多见的例子:闭包
for(var i=1;i<=5;i++) { setTimeout(function timer(){ console.log(i); },i*1000); }
以咱们所想,咱们可能认为他会输出1~5,每秒一次,每次一个。
但实际上,这段代码在运行时会以每秒一次的频率输出五次6。函数
这是为何?code
缘由是延迟函数会在循环结束时才执行,事实上,当定时器运行时即便每一个迭代中执行的是setTimeout(...,0),全部的回调函数依然是在循环结束后才会执行,所以会每次输出一个6出来。作用域
根据做用域的原理,实际状况:尽管循环中的五个函数是在各个迭代中分别定义的,可是他们都被封闭在一个共享的全局做用域中,所以实际上只有一个i。
因此全部函数共享一个i的引用时,循环结构让咱们误认为背后还有更复杂的机制在器做用,但实际上啥都木有,若是将延迟函数的回调重复定义五次,彻底不使用循环,那他同这段代码是彻底等价的。回调函数
解决方法以下:
咱们先试一下:io
for(var i=1;i<5;i++){ (function(){ setTimeout(function timer(){ console.log(i); },i*1000); })(); }
看似能够,但实际也没用,虽然这样写咱们有更多词法做用域了,的确每一个延迟函数都会将IIFE在每次迭代中建立的做用域封闭起来。
若是做用域是空的,那么仅仅将他们进行封闭是不够的。仔细看一下,咱们的IIFE只是一个什么都没有的空做用域,因此须要包含一点实际内容为咱们所用。console
他须要本身的变量,用来在每一个迭代中存储i的值:for循环
for(var i=0;i<=5;i++) { (function(){ var j=i; setTimeout(function timer(){ console.log(j); },j*1000); })(); }
ok,他运行如咱们所愿了!function
能够进行改进:变量
for(var i=1;i<=5;i++) { (function{ setTimeout(function timer(){ console.log(j); },j*1000); })(i); //i能够改动,只要你喜欢 }
在迭代内使用IIFE会为每一个迭代都生成一个新的做用域,使得延迟函数的回调能够将新的做用域封闭在每一个迭代内部,每一个迭代中都会包含一个具备正确值的变量供咱们访问。
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); }