跳到主要内容
轻盈的鱼

← 返回归档

同步、微任务、宏任务与 rAF:一次讲清事件循环调度

日期:分类:javascriptguide标签:事件循环MicrotaskMacrotaskrequestAnimationFramequeueMicrotask

浏览器主线程里,「现在跑」和「稍后跑」不是一回事。同步代码、微任务、宏任务、requestAnimationFrame 分属不同调度层。本文用一张心智模型把它们对齐,并附上可复现的控制台实验与实测结果——尤其是 rAF 与 setTimeout(0) 谁先谁后并不保证。

一句话总览

事件循环大致按这个节奏转:同步跑完 → 清空微任务队列 →(若该画了)样式/布局 → rAF → 绘制 → 再取下一个宏任务。不同环境细节会有出入,但用来选 API、排 bug 已经够用。

四类任务分别干什么

1. 同步任务(Sync)

当前调用栈上直接执行的代码。立刻跑、不排队;跑完之前任何异步回调都进不来。太长会卡死页面。

2. 微任务(Microtask)

当前同步结束后、渲染之前要清空的一队回调。常见来源:queueMicrotask、Promise.then/catch/finally(含 async/await 续体)、MutationObserver。同步一结束就跑,且会一口气清空整队;比 setTimeout(0) 早。适合尽快续上逻辑;不适合大计算或必须先画一帧的事。

3. 宏任务(Macrotask)

事件循环里「下一轮任务」的队列。常见来源:setTimeout / setInterval、用户事件、网络完成、MessageChannel 等。比微任务晚;宏任务之间浏览器有机会渲染和处理输入。setTimeout(0) 也不是真 0ms。适合延后执行、拆长任务;不要用它精确对齐动画帧。

4. requestAnimationFrame(rAF)

登记在下一帧绘制之前跑的回调,跟屏幕刷新对齐。为视觉更新服务;后台标签页常被暂停或降频。与 setTimeout(0) 谁先谁后不保证。

实验:把四者放进同一段代码

在浏览器控制台直接粘贴运行:

1console.log('1 sync')
2
3queueMicrotask(() => console.log('2 micro'))
4
5Promise.resolve().then(() => console.log('3 promise micro'))
6
7setTimeout(() => console.log('5 macro'), 0)
8
9requestAnimationFrame(() => console.log('4 rAF'))
10
11console.log('1b sync end')

实测输出

某次在浏览器控制台中的实际结果:

1// 实际控制台输出:
2// 1 sync
3// 1b sync end
4// 2 micro
5// 3 promise micro
6// undefined // 控制台对整段脚本的返回值,可忽略
7// 5 macro
8// 4 rAF

解读:

  1. 1 sync → 1b sync end:同步先全部跑完,铁律。
  2. 2 micro → 3 promise micro:同属微任务队列,先入先出;queueMicrotask 与 Promise.then 同档。
  3. 5 macro 出现在 4 rAF 之前:说明这次微任务清空后浏览器没有立刻进入渲染,先处理了 setTimeout(0),真正要画时才跑 rAF。

为什么 4 和 5 不一定

微任务清空后,浏览器会决定现在要不要画一帧。要画则可能先 rAF 再宏任务;先不画则可能先宏任务再 rAF(就是上面这份实测)。控制台、DevTools、标签是否在前台都会影响,因此不要把 rAF 与 setTimeout(0) 的先后写死。

可靠顺序 vs 不可靠顺序

  • 同步 vs 一切异步:同步一定先完
  • 微任务 vs 宏任务:微任务一定更早
  • 微任务 vs rAF:一般微任务更早(在渲染前清空)
  • rAF vs setTimeout(0):不一定

怎么选

  • 现在立刻算完 → 同步(但别太久)
  • 当前逻辑回合末尾马上续上 → queueMicrotask / Promise.then
  • 故意推后 / 让出主线程 → setTimeout 等宏任务
  • 跟下一帧绘制走 → requestAnimationFrame
  • 写顺序断言 → 只断言同步 → 微任务;不要断言 rAF 与宏任务先后

同族 API 速查

  • 微任务档:queueMicrotask、Promise.then、MutationObserver
  • 帧 / 空闲:requestAnimationFrame、requestIdleCallback、ResizeObserver、IntersectionObserver
  • 宏任务档:setTimeout、setInterval、MessageChannel、scheduler.postTask
  • Node 特有:process.nextTick、setImmediate

小结

同步是正在干的活;微任务是刚收尾立刻补上的尾巴;rAF 是下一帧画画前的钩子;宏任务是下一轮事件循环的活。同步 → 微任务是铁律;rAF 与宏任务可以互换顺序,代码里别赌。把上面那段实验再跑几遍,会更容易记住「可靠」和「不可靠」的边界。

版权信息

日期2026/08/07
协议CC BY-NC-SA 4.0 转载请注明出处
评论

暂无评论,来抢沙发吧。