setTimeout(fn, 0) 的作用

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout 消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间,而非确切的时间。

零延迟 (Zero delay) 并不是意味着回调会立即执行。在零延迟调用 setTimeout 时,其并不是过了给定的时间间隔后就马上执行回调函数。其等待的时间基于队列里正在等待的消息数量。也就是说,setTimeout() 只是将事件插入了任务队列,必须等到当前代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是当前代码耗时很长,有可能要等很久,所以并没有办法保证回调函数一定会在 setTimeout() 指定的时间执行。

例子

  1. setTimeout(function() {
  2. console.log(1);
  3. },0);
  4. console.log(2)复制代码

执行结果2,1。因为只有在执行完第二行以后,主线程空了,才会去任务队列中取任务执行回调函数。

setTimeout(fn,0) 的含义是,指定某个任务在主线程最早可得的空闲时间执行,也就是说,尽可能早得执行。它在”任务队列”的尾部添加一个事件,因此要等到主线程把同步任务和”任务队列”现有的事件都处理完,才会得到执行。
在某种程度上,我们可以利用setTimeout(fn,0)的特性,修正浏览器的任务顺序。

例子

  1. let a = 1
  2. function fn(){
  3. setTimeout(() => {console.log(a)},0)
  4. }
  5. fn()
  6. a = 2
  7. //打印的结果为2

为什么打印的结果是2不是 1 呢?
a 打印的时机是在 a = 2 之后,只有当任务队列中 a = 2 执行完了,才会执行 setTimeout 中的回调函数

例子

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

执行结果为6,6,6,6,6,6
上面的代码中,变量i是var命令声明的,所以在全局范围内都有效,所以全局只有一个变量i,每一次循环,变量i的值都会发生改变,setTimeout函数中的i,指向的就是全局的i。也就是说,每次循环加在任务队列后面的console.log(i)中的i,指向的都是同一个i,循环结束输出的i的值为6,最后的结果也就是6个6。

在执行for循环任务时,setTimeout在「任务队列」的尾部添加6次 console.log(i)任务,当任务for循环执行完之后,i = 6,然后开始执行console.log(i),打印出6个6。

例子

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

执行结果为 0,1,2,3,4,5
上面的代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,也就是说每次的console.log(i)中的i是互不影响的。所以最后的结果是0,1,2,3,4,5。

参考链接