JS 函数的执行时机

105 阅读1分钟

我们可以用以下代码来分析JS的执行时机:

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

问i的结果为?

很简单,for循环总共执行6次。答案是0、1、2、3、4、5。

————————————————————————————————

可惜,答案是错误的!

正确答案是6个6 //666666

我们可以这样理解:

for循环执行步骤:

  1. i赋值为0
  2. 判断i < 6 ?,满足进入第一循环
  3. setTimeout()会过一会执行–>跳过setTimeout()继续执行
  4. 执行i++,此时i的值为1
  5. 判断i < 6 ?,满足进入第二循环
  6. setTimeout()会过一会执行–>跳过setTimeout()继续执行
  7. 执行i++,此时i的值为2
  8. 省略…
  9. 执行i++,此时i的值为6
  10. 判断i < 6 ?,不满足跳出循环
  11. 执行第一次循环的setTimeout() //打印出a
  12. 执行第二次循环的setTimeout() //打印出a
  13. 执行第三次循环的setTimeout() //打印出a
  14. 执行第四次循环的setTimeout() //打印出a
  15. 执行第五次循环的setTimeout() //打印出a
  16. 执行第六次循环的setTimeout() //打印出a
  17. 结束

现在可以看出,由于setTimeout()的执行时间为for语句执行后,所以每次打印出的结果都为6

setTimeout()的'过一会'执行究竟是多久呢?

上文中不止一次提到'过一会'了,那么'过一会'究竟是多久呢?

setTimeout(fn,0)的含义是,指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。也就是当同步任务的函数和语句执行完后,0秒或者立刻执行setTimeout(fn,0)。

那我就是想要打印0、1、2、3、4、5呢?

如果对于for循环使用setTimeout()的循行结果不满意,我们就是要打印0、1、2、3、4、5呢?

可以这样写:

for(let i = 0; i<6; i++){
    setTimeout(()=>{
        console.log(i)
    },0)
}
// 0 1 2 3 4 5

解释: 因为let变量的作用域只能在当前函数中,所以每次for循环生成的都是一个新的i, setTimeout里输出的i就是这个新的i,这个i是不会变化的,所以输出的就是正常的。

因为在for语句里用let声明变量是局部变量遵循块作用域,所以每次for循环执行时都会生成一个单独的作用域,也会生成一个新的i,相当于有6个 i。 此时,每次执行setTimeout()时都会打印出对应的i,打印结果就是0、1、2、3、4、5了。

我们还可以这样解决

  • 闭包
let i 
for(i = 0; i<6; i++){
  !function(j){
      setTimeout(()=>{
        console.log(j)
      },0)
  }(i)
}
  • 利用 setTimeout 的第三个参数,将i传进去
let i 
for(i = 0; i<6; i++){
  !function(j){
      setTimeout(()=>{
        console.log(j)
      },0)
  }(i)
}
  • 利用 const 关键字
let i
for(i = 0; i<6; i++){
    const x = i
    setTimeout(()=>{
      console.log(x)
    })
}