在javascript执行机制中,setTimeout是在主线程的同步代码一开始执行的时候,就已经开始倒计时了,还是说在主线程执行完后,读取事件队列中异步任务的时候,才开始倒计时? 最近看了javaScript执行机制相关的几篇文章,知道了javascript引擎会在主线程执行完同步任务后,读取事件队列的异步任务来执行,文章都有拿setTimeout 作为异步任务来举例,但对于setTimeout,是在主线程中的同步代码一开始执行的时候,就已经开始倒计时了,还是说在主线程执行完后,读取事件队列中异步任务的时候,才开始倒计时,描述比较模糊,甚至有冲突的地方,有查MDN,但是,没有相关深入说明,网上也很难找到相关资料,有写代码来验证,但觉得验证方法有些牵强,有相关资料推荐吗?
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引擎开始执行代码时: script标签里包含的就是第1个宏观任务,开始从上往下执行; 遇到setTimeout,将回调插入到宏观任务中200ms的位置,等待执行; 遇到new Promise,执行里面的代码; 发现有then,将then中的回调插入到微观任务中等待执行; 此时script标签的宏观任务执行完毕,查看微观队列中是否有任务,有,then的回调,执行; 全部的微观任务执行完毕后,查看宏观任务的队列中是否有任务,有,刚才setTimeout的回调,执行; 再次查看微观任务队列,无,再次查看宏观任务队列,无,执行完毕; 由此我们再引出,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 。
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 环境)。
我们以下面的这段代码为例:
当js引擎开始执行代码时:
setTimeout
,将回调插入到宏观任务中200ms的位置,等待执行;new Promise
,执行里面的代码;then
,将then中的回调插入到微观任务中等待执行;由此我们再引出,
setTimeout
是什么时候开始倒计时的,是在执行到当前位置的时候,就已经开始计时了,把回调里的代码插入到200ms后的位置等待执行。但我们听说setTimeout计时不准确的原因就在于:当把回调插入到宏观任务队列中后,后续如果有特别耗时的任务,那么回调的代码必然要等待这些代码执行完毕后,才能执行。例如我们在setTimeout后面添加随机生成500万个随机数,然后对这些数进行排序,这是一个很耗时的操作,在我的电脑上测试时大概需要1400ms左右完成,那么setTimeout至少也要等到1400ms后才能执行。
例如下面的这段代码,我们本来要设定200ms毫秒后执行,但实际上回调执行的时候,已经在1400ms(各电脑产生的数据不一样)之后了。
参考: 前端中的事件循环eventloop机制