JS函数执行时机

201 阅读3分钟

下述代码为什么会打出六个六?

let i = 0
for(i = 0; i<6; i++){
  setTimeout(()=>{
    console.log(i)
  },0)
}
  • setTimeout函数的意思是过一会执行,具体过多长时间由后面的参数决定,但是虽然参数是0,也要过一会才执行,而不是立刻执行setTimeout里面的代码。上述代码是for循环里面有setTimeout函数,根据我上面所说,他是先执行for循环,执行完了在执行setTimeout函数,就像我们先打游戏在吃饭一样,for循环就是打游戏,setTimeout函数就是吃饭,先执行完for循环,在进行setTimeout,所以会打出六个六。

但是对于像我这样JS小白们来说,按道理上述代码不应该是打印出 0,1,2,3,4,5吗?但实际上就是打印出了六个六,那么怎么才能打印出0,1,2,3,4,5呢?JS为了迎合像我这样的小白的想法,只需对上述代码做一点点改动,就能打印出六个六:

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

这样就能打印出0到5了,原因是:

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

  • 那么还有其他方法使上述代码打印出0到5么?

  • 有!使用立即执行函数!

let i = 0
for(i = 0; i<6; i++){
  !function(x) {setTimeout(()=>{
    console.log(x)
  },0)}(i)
}
  • 另一种,使用setTimeout的第三个参数
let i = 0
for(i = 0; i<6; i++){
  setTimeout((x)=>{
    console.log(x)
  },0,i)
}

下面是搜到的原因!

for循环是同步代码,setTimeout中的是异步代码

JS碰到这个有同步和异步的情况下:

  1. 会先从上到下执行同步代码
  2. 碰到异步的代码会将其插入到任务队列当中等待
  3. setTimeout是延时,碰到setTimeout这个异步的代码块会根据它里面的第二个参数,延时时间来将代码插入到任务队列当中,即使是0!

还有就是作用域

作用域是变量等资源的作用范围。在这段代码中准确的说是作用域链的问题,当同步代码执行完毕开始执行异步的setTimeout代码时,setTimeout中需要一个变量i,而执行的时候在当前的作用域中开始找,找不到变量i的定义,这个> 时候就把创建这个函数的作用域作为当前作用域,再次寻找,创建这个函数的作用域就是全局作用域,也就是找到了for循环中i,找到了之后就结束寻找变量i的行程。由于这个时候的i是全局的,而且人家已经变为了最终形态:6,setTimeout找到的就是这个i=6;所以就输出了6,下面的5次setTimeout 的执行都是类似,所以结果都是6。