事件循环是面试常客,也是日常踩坑的来源。这次用时间轴的方式重新梳理一遍。

一次循环长什么样

  1. 执行一个宏任务:一段 script、setTimeout 回调、IO 事件等;
  2. 清空微任务队列:Promise.then、queueMicrotask、MutationObserver;
  3. 必要时渲染:requestAnimationFrame 回调在渲染前执行,然后才是样式计算、布局、绘制。

注意微任务队列要「完全清空」才会进入下一步——循环中新增的微任务也会在本轮执行完。

为什么 Promise 比 setTimeout 先执行

因为两者根本不在同一个阶段:setTimeout 回调要等下一个宏任务,而 Promise.then 在当前宏任务结束后立刻以微任务运行。所以下面代码稳定输出 promise 再输出 timeout:

setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));

rAF 与 setTimeout 的竞争

rAF 的触发时机与屏幕刷新对齐(通常 16.7ms 一次),setTimeout(0) 的实际间隔受嵌套层数影响(4ms 起)。动画一律用 rAF,轮询类任务再考虑 setTimeout。

小结

把「宏任务 → 微任务 → 渲染」当成一条固定流水线,大多数执行顺序题都可以推导,不需要背。