公众号【码农爱摸鱼】,专注于在摸鱼中愉快的工作和学习~
我们知道 JS 是单线程的,同一时间只能执行一件事情。
而在我们前端使用框架书写页面代码时,就算是大数据量的渲染和 dom 操作,也不会在浏览器上体会出很强的卡顿。
这得力于在我们使用的框架中,均在处理数据和浏览器渲染这俩个事件上做过代码处理已达到减轻或者去除可能会导致浏览器渲染卡顿的情况。
而在 React 中,于 16 版本之后对此渲染处理的模块单独做过优化行成了新的调度策略(Fiber),今天我们就来浅浅的了解一下。
浏览器渲染
首先,咱来了解一下,在浏览器中页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到 60 时,页面是流畅的,小于这个值时,用户会感觉到卡顿。 1s 60 帧,所以每一帧分到的时间是 1000/60 ≈ 16 ms。所以我们书写代码时力求不让一帧的工作量超过 16ms。
在一帧中浏览器所需要完成的事情
通过图可看到,一帧内需要完成如下六个步骤的任务:
- 1.处理用户的交互
- 2.JS 解析执行
- 3.帧开始。窗口尺寸变更,页面滚去等的处理
- 4.requestAnimationFrame(rAF)
- 5.布局
- 6.绘制
了解了上面的生命周期之后,我们再来看 2 个函数:
requestIdleCallback
上面六个步骤完成后没超过 16 ms,说明时间有富余,此时就会执行requestIdleCallback
里注册的任务。 下面是一个简单的示例:
scss
requestIdelCallback(task);
function task (deadline) {
// deadline.timeRemaining()可以获取到当前帧剩余时间
while (deadline.timeRemaining() > 0 && tasks.length > 0) {
doWorkIfNeeded();
}
if (tasks.length > 0){
requestIdleCallback(task);
}
}
requestAnimationFrame
requestAnimationFrame
方法就是告诉浏览器你希望执行一个动画。
并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。
上面这句话引自 MDN
说通俗点就是,该 API 能以浏览器的显示频率来作为其动画动作的频率,例如某一设备的刷新率是 75 Hz,那这时的时间间隔就是 13.3 ms(1 秒 / 75 次),动画回调也每 13.3ms 调用一次;
Fiber
Fiber 架构的功能就是来处理这类渲染问题,其主要思想如下:
-
解决主线程长时间被 JS 运算占用这一问题的基本思路,是将运算切割为多个步骤,分批完成。也就是说在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间进行页面的渲染。等浏览器忙完之后,再继续之前未完成的任务。
-
React 将任务分成小片,在一小片段的时间内运行这些分片任务,让主线程做优先级更高的事情,如果有任何待处理的事情,就回来完成工作。一个 Fiber 就是一个工作单元, React 的一个核心概念是 UI 是数据的投影 ,组件的本质可以看作输入数据,输出 UI 的描述信息(虚拟 DOM 树),即:
ui = f(data)
也就是说,渲染一个 React app,其实是在调用一个函数,函数本身会调用其它函数,形成调用栈,递归调用导致的调用栈我们本身无法控制, 只能一次执行完成。而 Fiber 就是为了解决这个痛点,可以去按需要打断调用栈,手动控制 stack frame------就这点来说,Fiber 可以理解为 virtual stack frame。
-
旧版 React 通过递归的方式进行渲染,使用的是 JS 引擎自身的函数调用栈,它会一直执行到栈空为止。而 Fiber 实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的 requestIdleCallback 这一 API。
而对此,官方的解释是这样的:
window.requestIdleCallback()
会在浏览器空闲时期依次调用函数,这就可以让开发者在主事件循环中执行后台和低优先级的任务,而且不会对像动画和输入响应等用户交互这些延迟触发但关键的事件产生影响。函数一般会按先进先调用的顺序执行,除非函数在浏览器调用它之前就到了它的超时时间。
而基于这些思想,最终完成了 Fiber 的优化,至于 Fiber 的核心内容和代码,咱后面再一起继续研究 🧐
我是摸鱼君,你的【三连】就是摸鱼君创作的最大动力,如果本篇文章有任何错误和建议,欢迎大家留言!
文章持续更新,可以微信搜索 【码农爱摸鱼】关注公众号第一时间阅读。