43、requestAnimationFrame 原理?是同步还是异步?
目录一、先给一个面试里的标准回答二、requestAnimationFrame 是什么基本用法三、它的原理是什么四、它是同步还是异步结论为什么说它不是同步但它和普通异步又不完全一样五、requestAnimationFrame 和事件循环是什么关系一个常见执行顺序例子六、为什么 requestAnimationFrame 比 setTimeout 更适合动画1. 它和屏幕刷新节奏同步2. 避免无效渲染3. 后台标签页会自动降频或暂停4. 更流畅七、requestAnimationFrame 的时间参数是什么为什么这个参数有用示例八、底层原理怎么理解九、常见代码写法1. 一次执行2. 连续动画3. 取消动画十、面试中容易被追问的点追问1requestAnimationFrame 一定是 16.7ms 执行一次吗追问2页面切到后台会怎样追问3它和 setTimeout(fn, 16) 的本质区别是什么追问4它属于宏任务还是微任务十一、实际开发中适合用在哪1. JS 动画2. 滚动联动、拖拽节流3. 视觉更新节流十二、不适合用在哪1. 纯业务延时逻辑2. 不依赖渲染时机的异步任务十三、面试中怎么回答更精彩高分回答模板十四、一个更适合背诵的精简版十五、一句话总结这是一个很高频的问题而且很容易被问出层次它是干什么的和setTimeout有什么区别它什么时候执行是同步还是异步为什么它更适合做动画底层原理是什么如果你面试里只说requestAnimationFrame是浏览器提供的动画 API。这太浅了。更好的回答要讲清楚执行时机、和浏览器渲染流程的关系、以及为什么它适合动画。一、先给一个面试里的标准回答requestAnimationFrame是浏览器提供的一个用于在下一次重绘前执行回调的 API主要用于高性能动画和视觉更新。它的核心原理是浏览器会在每一帧渲染之前统一执行requestAnimationFrame注册的回调然后再进行样式计算、布局、绘制。从执行模型上看它不是同步代码而是异步调度因为回调不会立刻执行而是要等浏览器下一帧调度时再执行。相比setTimeout它的优点是能和浏览器刷新节奏对齐通常是 60Hz也就是大约 16.7ms 一帧这样动画更流畅也避免了无效渲染如果页面处于后台浏览器通常还会暂停或降频执行从而节省性能开销。这段已经很像高分回答了。二、requestAnimationFrame是什么它的作用可以简单理解为告诉浏览器我有一段和视觉更新相关的代码请你在下一次页面重绘之前帮我执行。基本用法requestAnimationFrame(() { console.log(在下一帧渲染前执行) })如果要持续做动画一般会递归调用function animate() { console.log(更新动画) requestAnimationFrame(animate) } requestAnimationFrame(animate)三、它的原理是什么这是面试重点。浏览器页面渲染并不是随时随地发生的它通常按“帧”来更新。在大多数设备上屏幕刷新频率大约是60 次/秒也就是每一帧约16.7ms。浏览器每一帧大致会做这些事处理用户输入执行 JS处理requestAnimationFrame回调样式计算布局Layout绘制Paint合成Composite显示到屏幕所以requestAnimationFrame的核心原理就是把回调安排到浏览器下一次重绘之前执行。这样做的好处是更新 DOM 的时机更合理避免和浏览器渲染节奏错位动画更平滑减少不必要的重绘四、它是同步还是异步这是很多人会答模糊的地方。结论requestAnimationFrame是异步的。因为你调用它时回调不会立刻执行而是被浏览器放到“下一帧重绘前”的时机去执行。为什么说它不是同步同步代码的特征是调用后当前调用栈里立刻执行。比如console.log(1) console.log(2)这是同步。而requestAnimationFrameconsole.log(1) requestAnimationFrame(() { console.log(2) }) console.log(3)输出通常是1 3 2说明它不是当前调用栈里立即执行而是延后到后续调度阶段执行所以它是异步调度。但它和普通异步又不完全一样它不是Promise那种微任务也不是普通的宏任务setTimeout。它属于浏览器渲染时机相关的异步回调也就是说它的执行和浏览器的渲染帧绑定而不是简单排进某个定时器队列。五、requestAnimationFrame和事件循环是什么关系这个问题如果答出来会显得你理解很深。浏览器一轮事件循环里大致有执行一个宏任务清空微任务可能进行渲染在渲染前执行requestAnimationFrame然后进行绘制你可以把它理解成requestAnimationFrame的回调会在浏览器准备渲染新一帧时触发它依附于浏览器渲染流程而不是单纯依附于 JS 引擎任务队列。一个常见执行顺序例子console.log(start) setTimeout(() { console.log(setTimeout) }, 0) Promise.resolve().then(() { console.log(promise) }) requestAnimationFrame(() { console.log(raf) }) console.log(end)典型情况下可能输出start end promise raf setTimeout但这里要注意raf和setTimeout的先后不能机械死记因为和浏览器是否进入渲染阶段、当前环境、帧时机都有关系。不过从理解上你可以说Promise微任务一定先于下一轮渲染requestAnimationFrame在渲染前执行setTimeout是定时器宏任务何时调度受事件循环影响六、为什么requestAnimationFrame比setTimeout更适合动画这是面试一定要讲的。1. 它和屏幕刷新节奏同步如果用setTimeout(fn, 16)做动画看起来像是 16ms 执行一次但其实定时器不精确可能丢帧可能在不该渲染的时候执行也可能一帧内执行多次而requestAnimationFrame是浏览器在要渲染这一帧之前统一调用一次所以天然更适合动画。2. 避免无效渲染浏览器一帧只会显示最终结果。如果你在一帧内多次修改样式中间很多修改用户根本看不到。requestAnimationFrame能把更新尽量对齐到渲染前减少浪费。3. 后台标签页会自动降频或暂停如果页面在后台setTimeout仍可能继续触发只是被浏览器限制频率requestAnimationFrame通常会暂停或大幅降频这样更省 CPU更省电。4. 更流畅因为它和浏览器绘制时机一致所以掉帧更少流畅度更高。七、requestAnimationFrame的时间参数是什么很多人会忽略这一点。requestAnimationFrame((timestamp) { console.log(timestamp) })回调参数timestamp是一个高精度时间戳表示当前回调被执行的时间。它通常和performance.now()是同一时间基准。为什么这个参数有用做动画不能简单假设每一帧都正好 16.7ms因为设备性能不同帧率可能变化。所以更合理的方式是根据真实时间差计算动画位移示例let start 0 function animate(timestamp) { if (!start) start timestamp const progress timestamp - start console.log(已运行时间:, progress) if (progress 1000) { requestAnimationFrame(animate) } } requestAnimationFrame(animate)这样动画速度就不会强依赖帧率。八、底层原理怎么理解从底层思想上看可以这么理解requestAnimationFrame并不是 JS 引擎自己单独决定执行的而是浏览器渲染引擎在每一帧开始绘制前检查有没有注册的动画回调有就执行执行完再去做样式计算、布局、绘制下一帧继续所以它的本质是浏览器提供给 JS 的一个“参与渲染节奏”的接口。它让 JS 不再“瞎猜”什么时候更新 UI而是直接跟浏览器说到你要绘制下一帧的时候通知我我再更新。九、常见代码写法1. 一次执行requestAnimationFrame(() { element.style.left 100px })2. 连续动画let x 0 function move() { x 2 box.style.transform translateX(${x}px) if (x 300) { requestAnimationFrame(move) } } requestAnimationFrame(move)3. 取消动画requestAnimationFrame会返回一个 id可以取消。const id requestAnimationFrame(() { console.log(动画) }) cancelAnimationFrame(id)十、面试中容易被追问的点追问1requestAnimationFrame一定是 16.7ms 执行一次吗不一定。16.7ms 只是 60Hz 屏幕下的大致间隔。如果屏幕是 120Hz间隔可能更短如果主线程繁忙或者页面卡顿也可能更长。所以不能把它当成固定 16ms 的定时器。追问2页面切到后台会怎样浏览器通常会暂停或降低requestAnimationFrame的执行频率因为后台页面不需要高频重绘这也是它比setTimeout更节能的原因。追问3它和setTimeout(fn, 16)的本质区别是什么setTimeout是时间驱动不关心浏览器是否要渲染requestAnimationFrame是渲染驱动回调执行和浏览器下一帧绘制绑定。所以前者更像定时器后者更像动画调度器。追问4它属于宏任务还是微任务严格来说不建议简单粗暴地把requestAnimationFrame归类成宏任务或微任务。更准确的说法是它属于浏览器在渲染前调度的回调不是Promise微任务也不是典型的setTimeout宏任务。如果面试官非要你归类你可以说它不属于微任务通常也不直接按常规定时器宏任务去理解更准确是“渲染阶段的异步回调”。这个回答更稳。十一、实际开发中适合用在哪1. JS 动画function step() { // 更新位置 requestAnimationFrame(step) }2. 滚动联动、拖拽节流比如滚动事件触发很频繁你可以用requestAnimationFrame合并更新let ticking false window.addEventListener(scroll, () { if (!ticking) { requestAnimationFrame(() { console.log(window.scrollY) ticking false }) ticking true } })这样能避免滚动中频繁执行 DOM 操作。3. 视觉更新节流适合拖拽resizemousemove大量 DOM 更新的合并处理十二、不适合用在哪1. 纯业务延时逻辑比如3 秒后弹窗5 秒后请求接口这类更适合setTimeout2. 不依赖渲染时机的异步任务如果不是 UI 更新就没必要强绑到渲染帧上。十三、面试中怎么回答更精彩你要避免“只背定义”最好讲出它和浏览器渲染流程的关系。高分回答模板requestAnimationFrame是浏览器提供的一个动画调度 API它会把回调安排在下一次页面重绘之前执行。它的核心原理是浏览器每一帧在真正绘制之前会先执行这一帧里注册的requestAnimationFrame回调然后再做样式计算、布局和绘制所以它特别适合做动画和视觉更新。从执行模型上说它不是同步执行而是异步的因为调用后回调不会立即进入当前调用栈而是等待浏览器下一帧调度。和setTimeout相比它最大的优势是能和浏览器刷新节奏对齐避免丢帧和无效渲染而且页面进入后台时通常会自动降频或暂停更节能。所以我一般把它理解成一个“和浏览器渲染时机绑定的异步回调机制”而不是普通定时器。十四、一个更适合背诵的精简版requestAnimationFrame是浏览器提供的在下一次重绘前执行回调的 API主要用于动画。它的回调不是立即执行的所以本质上是异步的它和setTimeout的区别在于setTimeout是按时间触发requestAnimationFrame是按浏览器渲染帧触发因此更适合做动画性能也更好。十五、一句话总结requestAnimationFrame是一种和浏览器渲染帧绑定的异步回调机制适合做动画和高频视觉更新。