事件循环是面试常客,也是日常踩坑的来源。这次用时间轴的方式重新梳理一遍。
一次循环长什么样
- 执行一个宏任务:一段 script、setTimeout 回调、IO 事件等;
- 清空微任务队列:Promise.then、queueMicrotask、MutationObserver;
- 必要时渲染: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。
小结
把「宏任务 → 微任务 → 渲染」当成一条固定流水线,大多数执行顺序题都可以推导,不需要背。