setTimeout 是什么时候开始倒计时的?

在javascript执行机制中,setTimeout是在主线程的同步代码一开始执行的时候,就已经开始倒计时了,还是说在主线程执行完后,读取事件队列中异步任务的时候,才开始倒计时?

最近看了javaScript执行机制相关的几篇文章,知道了javascript引擎会在主线程执行完同步任务后,读取事件队列的异步任务来执行,文章都有拿setTimeout 作为异步任务来举例,但对于setTimeout,是在主线程中的同步代码一开始执行的时候,就已经开始倒计时了,还是说在主线程执行完后,读取事件队列中异步任务的时候,才开始倒计时,描述比较模糊,甚至有冲突的地方,有查MDN,但是,没有相关深入说明,网上也很难找到相关资料,有写代码来验证,但觉得验证方法有些牵强,有相关资料推荐吗?

阅读 3.3k
2 个回答

setTimeout 主要还是从单线程eventloop这两个机制方面开始了解,单线程表示 js 引擎在同一时间内只能执行一条执行,而 eventloop:前端的任务分为宏观任务(macroTask)和微观任务(microTask),分别维护两个队列,setTimeout 的回调属于宏观任务,每执行完一个宏观任务,就查看微观任务中是否有任务,如果有则全部执行完毕后,才开始执行下一个宏观任务。

宏任务主要有:script(整体代码)、setTimeout、setInterval、I/O、UI 交互事件、postMessage、MessageChannel、setImmediate(Node.js 环境)。

微任务主要有:Promise.then、 MutationObserver、 process.nextTick(Node.js 环境)。

我们以下面的这段代码为例:

setTimeout(() => {
    console.log('setTimeout callback');
}, 200);
new Promise((resolve, reject) => {
    console.log('new Promise');
}).then(() => {
    console.log('Promise.then');
})

当js引擎开始执行代码时:

  1. script标签里包含的就是第1个宏观任务,开始从上往下执行;
  2. 遇到setTimeout,将回调插入到宏观任务中200ms的位置,等待执行;
  3. 遇到new Promise,执行里面的代码;
  4. 发现有then,将then中的回调插入到微观任务中等待执行;
  5. 此时script标签的宏观任务执行完毕,查看微观队列中是否有任务,有,then的回调,执行;
  6. 全部的微观任务执行完毕后,查看宏观任务的队列中是否有任务,有,刚才setTimeout的回调,执行;
  7. 再次查看微观任务队列,无,再次查看宏观任务队列,无,执行完毕;

由此我们再引出,setTimeout是什么时候开始倒计时的,是在执行到当前位置的时候,就已经开始计时了,把回调里的代码插入到200ms后的位置等待执行。

但我们听说setTimeout计时不准确的原因就在于:当把回调插入到宏观任务队列中后,后续如果有特别耗时的任务,那么回调的代码必然要等待这些代码执行完毕后,才能执行。例如我们在setTimeout后面添加随机生成500万个随机数,然后对这些数进行排序,这是一个很耗时的操作,在我的电脑上测试时大概需要1400ms左右完成,那么setTimeout至少也要等到1400ms后才能执行。

例如下面的这段代码,我们本来要设定200ms毫秒后执行,但实际上回调执行的时候,已经在1400ms(各电脑产生的数据不一样)之后了。

var startTime = window.performance.now();
console.log('setTimeout start', startTime);
setTimeout(() => {
    console.log('setTimeout callback, consuming: ' + (window.performance.now() - startTime) + ' ms');
}, 200);
console.log('sort start');
var sum = function(a, b) {
    return Number(a) + Number(b);
}
var res = [];
for(var i=0; i<5000000; i++) {
    var a = Math.floor(Math.random()*100);
    var b = Math.floor(Math.random()*200);
    res.push(sum(a, b));
}
res = res.sort();
console.log('sort end');

参考: 前端中的事件循环eventloop机制

跟据 HTML标准 里关于 setTimeout 的说明,setTimeout 是在调用的时候就开始计时的。计时可以认为并不发生在 js 线程,与 event loop 是并行的。计时完成之后,回调的 task 才会加入 task queue 。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏