2024-03-02 23:40:59
fre调度器的实现思路是通过分离requestAnimationFrame(RAF)和MessageChannel(MC)的职责,利用RAF启动帧循环并计算时间阈值,将实际调度逻辑完全交由MC处理,从而在保证浏览器重绘时间的前提下实现高效、稳定的高优先级任务调度。 以下是具体实现思路的分步解析:
一、核心设计原则:分离RAF与MC的职责RAF的作用
仅在每一帧浏览器重绘前执行一次,用于启动帧循环并计算当前帧的时间阈值(即浏览器可用于执行JS任务的时间窗口)。
缓存关键变量(如帧开始时间、剩余时间等),为后续调度提供基准数据。
不再参与实际任务调度,避免动态调整帧率带来的复杂性。
MC的作用
在浏览器重绘后执行,作为实际调度任务的容器。
通过递归调用postMessage实现异步循环,在MC的回调函数中执行任务队列的调度逻辑。
完全接管任务分发,确保高优先级任务在阈值时间内完成。
早期版本的缺陷
Fre v1:将时间阈值写死(如固定16ms),导致浏览器重绘时间不足或空闲时间浪费。
React旧版:通过RAF动态调整帧率,但实现复杂且易受外部因素干扰。
Fre的改进方案
静态阈值优化:RAF在帧开始时计算理论可用时间(如16ms - 已用时间),MC根据此阈值控制任务执行量。
避免动态调整:通过分离启动与执行阶段,消除帧率波动对调度的影响,代码更简洁且可预测性更强。
帧启动阶段(RAF)
记录帧开始时间(performance.now())。
计算当前帧剩余时间(阈值):目标帧时间(如16ms) - 已用时间。
初始化任务队列,准备进入MC调度阶段。
任务执行阶段(MC)
从任务队列中取出高优先级任务,执行并监控耗时。
若任务未耗尽阈值时间,继续执行下一个任务;否则暂停调度,等待下一帧RAF重新计算阈值。
通过MessageChannel.postMessage递归触发下一轮调度,形成闭环。
React的复杂性来源
Concurrent Mode兼容性:早期React试图让ConcurrentMode作为普通组件嵌入任意子树,导致调度器需处理嵌套上下文、任务中断等复杂逻辑。
动态帧率调整:为平衡UI响应与任务执行,React通过RAF动态修改帧率,但增加了状态管理的负担。
Fre的简化策略
移除Concurrent Mode依赖:聚焦单一职责调度,避免嵌套组件带来的上下文切换开销。
固定阈值+MC执行:将调度逻辑简化为“RAF计算时间,MC执行任务”,降低实现复杂度。
Event Loop的缺陷
任务按队列顺序执行,无优先级区分,低优先级任务可能阻塞高优先级任务(如动画)。
开发者需手动控制异步任务时机(如setTimeout(fn, 0)),增加心智负担。
requestIdleCallback的不足
兼容性:仅现代浏览器支持,且API行为不一致(如阈值时间差异)。
可控性差:浏览器自主决定空闲时间,无法强制执行关键任务。
RAF+MC的优势
兼容性:虽IE10+支持,但可通过polyfill覆盖更多场景。
精确控制:通过阈值管理确保高优先级任务(如UI渲染)优先执行,避免卡顿。
Fre的实现思路为React等框架的调度器设计提供了重要参考,其“简化但果断”的策略在性能与可维护性之间找到了平衡点。