requestAnimationFrame(rAF)是浏览器在下一次重绘前、微任务执行完毕后同步调用的渲染前钩子,不进入事件循环队列,执行时机早于setTimeout(0)但晚于Promise.then。

requestAnimationFrame(rAF)不是直接插入事件循环队列的宏任务或微任务,而是由浏览器在**下一次重绘前**调度执行的特殊回调。它的介入时序取决于渲染管线节奏,而非 JS 事件循环的常规阶段。
它不走事件循环队列,但受其影响
rAF 回调不会像 setTimeout 或 Promise.then 那样排队进宏/微任务队列。浏览器会在准备下一帧绘制(通常每 16.67ms,即 60fps)时,主动收集并同步执行所有已注册的 rAF 回调,这个过程发生在:
- 当前 JS 执行栈清空后
- 微任务队列全部执行完毕后
- 样式计算、布局、绘制等渲染步骤之前(即“渲染前钩子”)
所以它看似“在事件循环中”,实则是浏览器在事件循环间隙(render phase)主动注入的执行时机,优先级高于 repaint,但晚于 microtasks。
与 setTimeout(0) 和 Promise.then 的时序对比
假设同一轮 JS 执行中注册了三者:
- Promise.then:本轮微任务末尾立即执行(最快)
- setTimeout(0):进入下一轮宏任务队列,等当前任务+微任务+渲染完成后才执行
- requestAnimationFrame:不进队列,但在下一次渲染帧触发时、且所有微任务跑完后执行——通常比 setTimeout(0) 更早,但比 Promise.then 晚
典型顺序(同一轮同步代码中注册):
同步代码 → Promise.then → rAF 回调 → 浏览器渲染 → setTimeout(0)
避免 rAF 被阻塞的关键实践
rAF 回调若执行过久,会拖慢整帧渲染,导致丢帧。需注意:
- 只做必要更新:如仅修改 CSS transform / opacity 等合成属性,避开 layout / paint
- 用 cancelAnimationFrame 及时清理不再需要的动画帧
- 复杂逻辑拆分:用 queueMicrotask 或 setTimeout(0) 把耗时计算移出 rAF 回调
- 对长动画考虑使用 IntersectionObserver + rAF 组合,非可视区域暂停更新
调试时序:用 performance.now() 定位执行点
不要依赖 console.log 时间戳判断顺序。用高精度时间戳确认真实时机:
console.log('start', performance.now());
Promise.resolve().then(() => console.log('microtask', performance.now()));
requestAnimationFrame(() => console.log('rAF', performance.now()));
setTimeout(() => console.log('timeout', performance.now()), 0);
// 输出典型顺序:start → microtask → rAF → timeout(rAF 在渲染前,timeout 在渲染后)

















