关于js的事件循环机制,这篇文章已经讲的很清晰了。
但在实操的过程中我又遇到了一个问题,在此记录一下。如有不对,还望大佬斧正。
宏任务的子队列是按照类型进行分类的,只有同类型宏任务才会进入同一个子队列,遵循该子队列的统一执行规则;不同类型宏任务(如定时器 vs DOM 事件)则按子队列优先级执行,无法直接比较。重要的是:同一类型的宏任务,无论在哪个阶段(同步代码、微任务、其他宏任务)创建,都会进入「同一个专属子队列」进行比较。
解释了以下代码宏任务2(微任务创建)会输出在 宏任务3 前面的原因了。
举例:
以下代码的输出结果是什么?
console.log('同步1');
// 宏任务1
setTimeout(() => {
console.log('宏任务1');
// 宏任务1中的微任务
Promise.resolve().then(() => {
console.log('宏任务1的微任务');
// 微任务中创建宏任务2
setTimeout(() => {
console.log('宏任务2(微任务创建)');
}, 0);
});
}, 0);
// 宏任务3(直接创建)
setTimeout(() => {
console.log('宏任务3');
}, 3000);
console.log('同步2');
最终执行结果:
同步1 → 同步2 → 宏任务1 → 宏任务1的微任务 → 宏任务2(微任务创建) → 宏任务3
执行流程:
阶段 1:执行全局同步代码(第一轮宏任务)
这是事件循环的起点,浏览器会先执行script标签内的所有同步代码:
console.log('同步1'); // 执行 → 输出「同步1」
// 宏任务1:setTimeout(延迟0ms)
setTimeout(() => { /* 回调 */ }, 0);
// 行为:回调被加入「定时器宏任务子队列」,标记“最早可执行时间 = 当前时间 + 0ms”
// 宏任务3:setTimeout(延迟3000ms)
setTimeout(() => { /* 回调 */ }, 3000);
// 行为:回调被加入「定时器宏任务子队列」,标记“最早可执行时间 = 当前时间 + 3000ms”
console.log('同步2'); // 执行 → 输出「同步2」
✅ 此时全局状态:
- 调用栈:空(同步代码执行完毕);
- 微任务队列:空;
- 定时器宏任务子队列:
[宏任务1(延迟0ms)、宏任务3(延迟3000ms)]。
阶段 2:执行微任务队列(空)→ 跳过 UI 渲染
同步代码执行完后,事件循环会先检查微任务队列 —— 此时微任务队列为空,浏览器直接进入下一轮事件循环,无需执行 UI 渲染。
阶段 3:执行定时器宏任务子队列(按规则筛选执行)
定时器子队列的核心是 “先筛选可执行任务,再按顺序执行”
子阶段 3.1:执行宏任务 1(延迟 0ms,已到期)
定时器子队列中,宏任务 1 的延迟时间已到,成为第一个可执行的宏任务:
console.log('宏任务1'); // 执行 → 输出「宏任务1」
// 宏任务1中的微任务
Promise.resolve().then(() => { /* 回调 */ });
// 行为:回调被加入「微任务队列」
✅ 此时全局状态:
- 调用栈:空;
- 微任务队列:
[宏任务1的微任务]; - 定时器宏任务子队列:
[宏任务3(延迟3000ms,未到期)]。
子阶段 3.2:执行微任务队列(清空所有微任务)
宏任务 1 的同步代码执行完后,立即执行微任务队列中的所有任务:
console.log('宏任务1的微任务'); // 执行 → 输出「宏任务1的微任务」
// 微任务中创建宏任务2:setTimeout(延迟0ms)
setTimeout(() => { /* 回调 */ }, 0);
// 行为:回调被加入「定时器宏任务子队列」,标记“最早可执行时间 = 当前时间 + 0ms”
✅ 此时全局状态:
- 微任务队列:空(已清空);
- 定时器宏任务子队列:
[宏任务2(延迟0ms,已到期)、宏任务3(延迟3000ms,未到期)]。
子阶段 3.3:执行宏任务 2(微任务创建,已到期)
微任务队列清空后,浏览器进入下一轮事件循环 —— 此时宏任务 2 的延迟时间已到,成为定时器子队列中第一个可执行任务:
console.log('宏任务2(微任务创建)'); // 执行 → 输出「宏任务2(微任务创建)」
✅ 此时全局状态:
- 微任务队列:空;
- 定时器宏任务子队列:
[宏任务3(延迟3000ms,未到期)]。
阶段 4:等待 3000ms → 执行宏任务 3
宏任务 2 执行完后,浏览器会持续检查定时器子队列 —— 直到宏任务 3 的「最早可执行时间」到期(约 3000ms 后),才会执行该任务:
console.log('宏任务3'); // 执行 → 输出「宏任务3」