for循环setTimeout输出1-10解决方式

4,472 阅读3分钟

问题来源

for (var i = 0; i< 10; i++){
   setTimeout((i) => {
   console.log(i);
   }, 0)
}

期望:输出1到10

为什么无法输出1到十

因为在es5中是没有块级作用域的,看起来上面的代码好像被括号包住了每个i应该都能输出,但是其实并不是这样。

模拟这段代码

var i=0;                                                    // 1
    if(i<10)                                                // 2
        setTimeout(function(){console.log(i)},100)          // 3
        i++                                                 // 4
        if(i<10)                                            // 5
            setTimeout(function(){console.log(i)},100)      // 6
            i++                                             // 7
        ...直到i不满足if语句条件终止代码

因为es5中没有块级作用域,省略了括号看起来更直观。在代码中看起来应该好像是从上往下依次执行,但其实不是的。在js执行过程中,当同步和异步代码同时存在时,异步代码会在同步代码全部执行完成后再调用。而setTimeout就是异步代码(就算延迟为零也是异步),它会在代码最后执行,因此执行顺序为1>2>4>5>7>**>3>6。最后才执行setTimeout这时候的i已经经过自增变成了10,所以最后输出的结果为十个10.

setTimeout什么时候调用

var begin=new Date().getSeconds()
console.log('现在是第'+begin+'秒')      // 1

setTimeout(function(){
    console.log('setTimeout开始执行')   // 2
},10)

while((new Date().getSeconds()-begin)<2){           //设置两秒间隔
    continue
}
console.log('现在是第'+(new Date().getSeconds())+'秒')     //3 

在上面代码中我手动设置了一个两秒间隔的同步代码,而setTimeout的触发时间设置为10毫秒。如果按照从上到下依次执行的情况来看,执行顺序应该为1>2>3。但是实际却是1>3>2。这就佐证了我上面的看法,setTimeout在所有同步代码执行完后才调用。那你可能会问,如果不按时执行,那setTimeout中设置的触发时间不是没用了吗。那就涉及到优先级的问题了,setTimeout会在指定时间后触发。如果同步代码执行时间(或者其他在它之前执行异步代码)大于设定时间,那么它将在其他代码执行完成后立即执行。

想要了解异步执行顺序可阅读这篇文章:Js 的事件循环(Event Loop)机制以及实例讲解

解决方法

  • 方法一
    for (var i = 0; i< 10; i++){
      setTimeout((i) => {
        console.log(i)
      }, 1000,i);
    }

最精简解决方案

  • 方法二
    for (let i = 0; i< 10; i++){
      setTimeout(() => {
       console.log(i) 
      }, 1000);
    }

最优解决方案,利用let形成块级作用域

  • 方法三
    for (var i = 0; i< 10; i++){
      ((i)=>{
        setTimeout(() => {
          console.log(i)
        },1000);
      })(i)
    }

IIFE(立即执行函数),类似于let生成了块级作用域。

  • 方法四
    for (var i = 0; i< 10; i++){
      setTimeout(console.log(i),1000);
    }

直接输出,没有延迟

  • 方法五
    for (var i = 0; i< 10; i++){
      setTimeout((()=>console.log(i))(),1000);
    }

同上

  • 方法六
    for (var i = 0; i< 10; i++){
      try{
        throw i
      }catch(i){
        setTimeout(() => {
          console.log(i)
        }, 1000)
      }
    }