文章目录

JavaScript事件循环机制与宏任务微任务执行顺序

发布于 2026-04-01 23:05:08 · 浏览 9 次 · 评论 0 条

JavaScript事件循环机制与宏任务微任务执行顺序

JavaScript 是单线程语言,但能处理异步操作(如网络请求、定时器),靠的就是事件循环机制。理解事件循环的关键,在于分清宏任务(macrotask)和微任务(microtask)的执行顺序。


识别任务类型

区分哪些代码属于宏任务,哪些属于微任务

  • 宏任务包括:

    • setTimeoutsetInterval
    • I/O 操作(如文件读取、网络请求)
    • UI 渲染
    • script 标签整体执行(即整个脚本的首次运行)
  • 微任务包括:

    • Promise.then / Promise.catch / Promise.finally
    • queueMicrotask
    • MutationObserver(用于监听 DOM 变化)

注意:async/await 本质是基于 Promise 的语法糖,因此 await 后的代码会进入微任务队列。


执行流程规则

JavaScript 引擎按以下步骤运行代码:

  1. 执行当前宏任务(例如整个 <script> 脚本或一个 setTimeout 回调)。
  2. 收集该宏任务中产生的所有微任务,放入微任务队列。
  3. 立即执行微任务队列中的全部任务,直到队列清空。
  4. 执行一次 UI 渲染(如果需要)。
  5. 从宏任务队列中取出下一个宏任务,重复上述过程。

关键点:微任务总是在当前宏任务结束后、下一个宏任务开始前全部执行完


实战验证执行顺序

观察以下代码的输出顺序:

console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');

执行步骤分解

  1. 整个脚本作为第一个宏任务开始执行。
  2. 打印 '1'
  3. 遇到 setTimeout,将其回调(打印 '2')加入宏任务队列
  4. 遇到 Promise.resolve().then,将其回调(打印 '3')加入微任务队列
  5. 打印 '4'
  6. 当前宏任务结束,立即执行所有微任务打印 '3'
  7. 微任务队列清空后,浏览器可能进行渲染。
  8. 从宏任务队列取出 setTimeout 回调 → 打印 '2'

最终输出顺序为:

1
4
3
2

嵌套场景下的执行顺序

当微任务中又产生新的微任务或宏任务时,规则依然适用:

console.log('start');

setTimeout(() => {
  console.log('timeout1');
  Promise.resolve().then(() => {
    console.log('promise in timeout1');
  });
}, 0);

Promise.resolve().then(() => {
  console.log('promise1');
  setTimeout(() => {
    console.log('timeout in promise1');
  }, 0);
  Promise.resolve().then(() => {
    console.log('promise2');
  });
});

console.log('end');

逐步分析

  • 主宏任务执行:

    • 打印 'start'
    • setTimeout 回调(含 'timeout1')入宏任务队列
    • 第一个 Promise.then(含 'promise1')入微任务队列
    • 打印 'end'
  • 主宏任务结束,执行微任务队列:

    • 打印 'promise1'
    • 其中的 setTimeout(含 'timeout in promise1')入宏任务队列
    • 其中的第二个 Promise.then(含 'promise2')入微任务队列
    • 继续执行新加入的微任务 → 打印 'promise2'
    • 微任务队列清空
  • 开始处理宏任务队列(先进先出):

    • 执行第一个 setTimeout打印 'timeout1'
    • 其中的 Promise.then(含 'promise in timeout1')入微任务队列
    • 当前宏任务结束,立即执行该微任务打印 'promise in timeout1'
  • 继续下一个宏任务:

    • 执行 'timeout in promise1'打印 'timeout in promise1'
    • 无新微任务,结束

最终输出:

start
end
promise1
promise2
timeout1
promise in timeout1
timeout in promise1

常见误区澄清

误解 正确理解
setTimeout(fn, 0) 会立刻执行” 实际是将 fn 放入宏任务队列,需等当前宏任务及所有微任务执行完才轮到它
“微任务只在 Promise 中出现” queueMicrotask 也能手动添加微任务
“多个 setTimeout 会按时间精确排序” 即使设为 0ms,也受浏览器最小延迟限制(通常 ≥4ms),且执行顺序依赖入队顺序

实用调试技巧

使用 queueMicrotask 明确控制微任务

console.log('A');
queueMicrotask(() => console.log('B'));
setTimeout(() => console.log('C'), 0);
console.log('D');

输出:

A
D
B
C

这验证了:queueMicrotask 的行为与 Promise.then 一致,都属于微任务。


总结执行优先级

任务执行的绝对优先级顺序为:

  1. 当前宏任务中的同步代码
  2. 所有已入队的微任务(按入队顺序,深度优先)
  3. 下一个宏任务(按入队顺序)

记住这个口诀
“宏任务一跑完,微任务全清空;清完再跑下一宏”

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文