JavaScript事件循环机制
JavaScript 的执行模型基于事件循环,其中任务分为两类:宏任务(Macro Task)和微任务(Micro Task)。理解这两者的区别和执行顺序对于掌握 JavaScript 的异步编程非常重要。
宏任务(Macro Task)
宏任务包括以下几种常见的操作:
setTimeoutsetIntervalsetImmediate(Node.js 环境)- I/O 操作
- UI 渲染
微任务(Micro Task)
微任务包括以下几种常见的操作:
Promise的回调函数(then,catch,finally)MutationObserverqueueMicrotask
执行顺序
- 同步代码:首先执行所有的同步代码,这些代码会直接进入主线程执行。
- 微任务队列:同步代码执行完毕后,立即执行微任务队列中的所有任务,直到微任务队列为空。
- 宏任务队列:微任务队列清空后,执行一个宏任务。宏任务执行完毕后,再次检查微任务队列,重复上述过程。
例子
通过一个例子来理解微任务和宏任务的执行顺序:
console.log('同步代码开始');
setTimeout(() => {
console.log('宏任务:setTimeout');
}, 0);
Promise.resolve().then(() => {
console.log('微任务:Promise.then');
});
queueMicrotask(() => {
console.log('微任务:queueMicrotask');
});
console.log('同步代码结束');
执行步骤
-
同步代码:
console.log('同步代码开始');输出同步代码开始console.log('同步代码结束');输出同步代码结束
-
微任务队列:
Promise.resolve().then回调进入微任务队列queueMicrotask回调进入微任务队列
-
执行微任务队列:
queueMicrotask回调执行,输出微任务:queueMicrotaskPromise.resolve().then回调执行,输出微任务:Promise.then
-
宏任务队列:
setTimeout回调执行,输出宏任务:setTimeout
最终输出顺序
同步代码开始
同步代码结束
微任务:queueMicrotask
微任务:Promise.then
宏任务:setTimeout
通过这个例子,我们可以清楚地看到同步代码、微任务和宏任务的执行顺序。
PS : setTimeout等时间相关的宏任务可能存在计时不准确的情况
setTimeout 在 JavaScript 中不准确的原因主要有以下几点:
-
JavaScript 是单线程的:JavaScript 运行在单线程环境中,意味着它一次只能执行一个任务。如果当前有其他任务在执行(一般来说是在处理宏任务队列以及微任务队列中的任务),
setTimeout的回调函数将被推迟执行,直到主线程空闲。 -
事件循环机制:
setTimeout的回调函数会被放入宏任务事件队列中,只有当调用栈为空时,事件循环才会从事件队列中取出回调函数执行。如果前面的任务耗时较长,setTimeout的回调函数会被延迟执行。补充:当无同步代码执行,微任务队列为空,宏任务队列中setTimeout前的任务完成之后回调函数才会继续执行。因此可能会存在在微任务队列中存有大量的微任务时,需要等到所有微任务执行完毕之后回调函数才会继续执行的情况。 -
系统计时器精度:不同的浏览器和操作系统对计时器的精度有不同的实现,可能会导致
setTimeout的实际延迟时间与预期不一致。 -
最小延迟时间:HTML5 规范规定,嵌套的
setTimeout调用的最小延迟时间为 4 毫秒。这意味着即使你设置的延迟时间为 0 毫秒,实际执行时也会有至少 4 毫秒的延迟。
以下是一个简单的示例,展示 setTimeout 的不准确性:
console.log('Start');
setTimeout(() => {
console.log('Timeout callback');
}, 1000);
const start = Date.now();
while (Date.now() - start < 2000) {
// 模拟一个耗时操作
}
console.log('End');
在这个示例中,由于主线程被一个耗时 2 秒的操作阻塞,setTimeout 的回调函数会被延迟执行,尽管设置的延迟时间为 1 秒。
------------2025年3月11日更新------------
根据参考文章做一点更新
事件循环在过去的说法中,任务分为两个队列,一个是宏任务,一个是微任务。但是现在已经没有了宏任务的说法了。
根据 W3C 的最新解释:
每个任务都有一个任务类型,同一个类型的任务必须在一个队列,不同类型的任务可以分属于不同的队列。在一次事件循环当中,浏览器可以根据实际情况从不同的队列中取出任务执行;
浏览器必须准备好一个微任务队列,微队列中的任务优先所有其他任务执行;
在 Chrome 的实现中,至少包含了下面的队列:
延时队列: 用于存放计时器到达后的回调任务,优先级 中;
交互队列: 用于存放用户操作后产生的事件任务,优先级 高;
微队列: 用户存放需要最快执行的任务,优先级最高;

浙公网安备 33010602011771号