浅谈循环中setTimeout执行顺序问题

(下面有见解一二)

 

期望:开始输出一个0,然后每隔一秒依次输出1,2,3,4。

 



for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
}



 结果:输出5。

原因:setTimeout 使函数延迟1s执行,而for循环执行完成还不到0.1秒,到执行函数的时候,其实 i 已经变成5了,因此console.log(i)输出5。

 

 

解决方法一:使用let块作用域。 



for (let i = 0; i < 5; i++) { 
setTimeout(function() { 
console.log(i); 
}, 1000 * i); 
}



 

 

解决方法二:加个闭包。



for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, 1000 * i);
})(i);



结果:开始输出一个0,然后每隔一秒依次输出1,2,3,4。

 

失败方法: 



for (var i = 0; i < 5; i++) {
(function() {
setTimeout(function() {
console.log(i);
}, 1000 * i);
})();
}



结果:输出 5。

原因:去掉函数的参数i,则函数内部没有对i保持引用。

 

 

解决方法三:



for (var i = 0; i < 5; i++) {
setTimeout((function(i) {
console.log(i);
})(i), i * 1000);
}



结果:立马输出0-4。

原因:setTimeout可以接受函数或者字符串作为参数,而给setTimeout传递了一个立即执行函数,该立即执行函数是undefined ,也就是说等价于setTimeout(undefined, ...)。立即执行函数会立即执行。

 

 

 

 

 见解一:

for(var i=0;i<5;i++){ 
    setTimeout(function(){ 
        console.log(i); 
 },1000)

setTimeout是异步执行的,1000毫秒后向任务队列里添加一个任务,只有主线上的全部执行完才会执行任务队列里的任务,所以当主线程for循环执行完之后 i 的值为5,这个时候再去任务队列中执行任务,i全部为5;

每次for循环的时候setTimeout都会执行,但是里面的function则不会执行被放入任务队列,因此放了5次;for循环的5次执行完之后不到1000毫秒;

1000毫秒后全部执行任务队列中的函数,所以就是输出五个5啦

 

假如把var换成let,那么输出结果为0,1,2,3,4;

因为let  i  的是区块变量,每个i只能存活到大括号结束,并不会把后面的for循环的  i  值赋给前面的setTimeout中的i;而var i  则是局部变量,这个 i 的生命周期不受for循环的大括号限制;

 

 

 

见解二:



for(var i=0;i<=3;i++){

  setTimeout(function() {  

    console.log(i)  

  }, 10);

}

答案:打印4次4

 

这道题涉及了异步、作用域、闭包

settimeout是异步执行,10ms后往任务队列里面添加一个任务,只有主线上的全部执行完,才会执行任务队列里的任务,当主线执行完成后,i是4,所以此时再去执行任务队列里的任务时,i全部是4了。对于打印4次是:



 每一次for循环的时候,settimeout都执行一次,但是里面的函数没有被执行,而是被放到了任务队列里面,等待执行,for循环了4次,就放了4次,当主线程执行完成后,才进入任务队列里面执行。



   (注意:for循环从开始到结束的过程,需要维持几微秒或几毫秒。)



当我把var 变成let 时



for(let i=0;i<=3;i++){ setTimeout(function() {  console.log(i)  }, 10);}



打印出的是:0,1,2,3



当解决变量作用域,



因为for循环头部的let不仅将i绑定到for循环快中,事实上它将其重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值重新被赋值。setTimeout里面的function()属于一个新的域,通过 var 定义的变量是无法传入到这个函数执行域中的,通过使用 let 来声明块变量,这时候变量就能作用于这个块,所以 function就能使用 i 这个变量了;这个匿名函数的参数作用域 和 for参数的作用域 不一样,是利用了这一点来完成的。这个匿名函数的作用域有点类似类的属性,是可以被内层方法使用的。



查了一下百度的一个答案:

setTimeout是一次执行函数,这里是10ms后执行,仅仅执行一次;for(var i=0;i<=3;i++),i的每次取值都是执行setTimeout这个函数,并没有执行setTimeout里面的function(即闭包函数),setTimeout里面的function是有setTimeout的定时触动的,也就是10ms后执行,也就是说i从0~3时,一共执行了4次的setTimeout()函数,此时的i的值是4,由于for语句的执行速度远小于1秒,所以,1秒后,由setTimeout()函数定时触动的闭包函数function()开始执行,alert(i);i的值已经是4了,所以相继打印4次i.



 

 

 

 

 

---------------------