requestanimationframe 既不属于宏任务也不属于微任务,它被归类为浏览器渲染流程中的独立阶段。在 javascript 事件循环中,当当前宏任务执行完毕且所有微任务清空后,浏览器会检查是否需要重绘页面;如果需要,会在绘制下一帧之前调用 raf 回调函数。

为什么 rAF 不被归入宏任务队列
requestAnimationFrame 的回调不会进入宏任务队列,因为它的触发时机与浏览器的屏幕刷新率(通常为 60Hz)紧密绑定,而不是由事件循环调度器随机安排。如果将其视为宏任务,其执行顺序将受到其他宏任务(如定时器、用户交互事件)的干扰,导致动画卡顿或不连贯。

function logPhase(name) {
console.log(`Start: ${name}`);
requestAnimationFrame(() => {
console.log('rAF Callback');
});
setTimeout(() => {
console.log('setTimeout (Macro)');
}, 0);
Promise.resolve().then(() => {
console.log('Promise (Micro)');
});
console.log(`End: ${name}`);
}
logPhase('Main');
运行上述代码,控制台输出顺序为:
1. Start: Main
2. End: Main
3. Promise (Micro)
4. rAF Callback
5. setTimeout (Macro)
这一结果证明,rAF 在微任务之后、下一个宏任务之前执行。它位于“样式计算”和“布局”之后,“绘制”和“合成”之前。
rAF 与 setTimeout 的执行时序对比
通过对比 requestAnimationFrame 和 setTimeout,可以清晰看到它们在事件循环中的相对位置。setTimeout(fn, 0) 是典型的宏任务,它在当前调用栈清空、微任务执行完毕后,被放入宏任务队列等待下一次轮询。而 rAF 则是在当前帧的渲染准备阶段被触发。

// 验证 rAF 与 Microtask 的顺序
console.log('1. Script Start');
Promise.resolve().then(() => {
console.log('2. Microtask');
});
requestAnimationFrame(() => {
console.log('3. rAF');
});
console.log('4. Script End');
预期输出:
1. Script Start
4. Script End
2. Microtask
3. rAF
这表明 rAF 晚于微任务执行。如果将 rAF 放在微任务之前,DOM 更新可能尚未完成,导致读取到的布局信息不准确。
基于 rAF 回调中 DOM 状态的具体问题
在 requestAnimationFrame 回调中,开发者可以直接访问最新的 DOM 布局信息。这是因为 rAF 的触发点位于浏览器完成样式计算(Style Calculation)和布局(Layout)之后。这意味着,如果在上一帧或当前同步代码中修改了 DOM 样式,rAF 回调中获取的 offsetWidth、getBoundingClientRect() 等属性值已经是经过浏览器重新计算后的最新值,无需手动触发强制同步布局。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











