Skip to content

浏览器长任务

浏览器为什么会卡顿?

浏览器主线程一次只能处理一个任务(任务按照队列执行),当任务超过某个确定的点时,准确说是50ms,就会被称为长任务Long Task

当长任务执行时,如果用户想要尝试与页面交互或者一个重要的渲染更新需要重新发生,那么浏览器会等到Long Task执行完之后,才会处理它们,结果就会导致交互和渲染的延迟

所以如果存在Long Task,对Load(加载时)和Runtime(运行时)的性能都有影响

阻塞主线程达到50ms或以上的任务会导致以下问题:

1、可交互时间延迟

2、严重不稳定的交互行为(轻击、单击、滚动、滚轮等)延迟(High/variable input latency)

3、严重不稳定的事件回调延迟

4、紊乱的动画和滚动

常见的连续不间断的且主UI线程繁忙50ms及以上的时间的场景有:

1、长耗时的事件回调

2、代价高昂的回流或其他重绘

3、浏览器在超过50ms的事件循环的相邻循环之间所作的工作

React理念

主流浏览器刷新频率为60Hz,即每(1000ms/60Hz)16.6ms浏览器刷新一次

JS可以操作DOM,GUI渲染线程和JS线程是互斥的,所以JS脚本执行和浏览器布局、绘制不能同时执行

在16.6ms事件内,需要完成以下工作:

JS脚本执行、样式布局、样式绘制

当JS执行事件过长,超过16.6ms,这次刷新就没有事件执行样式布局样式绘制了,会造成页面卡顿

React如何解决?

在浏览器每一帧的时间中,预留一些时间给JS线程,react利用这部分时间更新组件(源码是5ms)

当预留的时间不够用时,react将线程控制权交还给浏览器使其有时间渲染UI,react则等待下一帧时间的到来继续被中断的工作(时间分片)

React15:

  • Reconciler协调器——负责找出变化的组件

  • Renderer渲染器——负责将变化的组件渲染到页面上

React15架构的Reconciler中,mount的组件会调用mountComponent,update的组件会调用updateComponent,这两个方法都会递归更新子组件。递归执行,更新一旦开始民众图就无法中断,当层级很深时,递归更新时间超过16ms,用户交互就会卡顿

React16:

  • Scheduler调度器——调度任务的优先级,高优先级任务优先进入Reconciler

  • Reconciler协调器——负责找出变化的组件

  • Renderer渲染器——负责将变化的组件渲染到页面上

以浏览器是否有剩余时间作为任务中断的标准,那么需要一种机制,当浏览器有剩余时间时通知,(部分浏览器实现了requestIdleCallback,但由于浏览器的兼容性以及触发频率不稳定,受很多因素影响,所以React实现了功能更完备的requestIdleCallback polyfill,也就是Scheduler(除了空闲时触发回调的功能外,Scheduler还提供了多种调度优先级任务的设置))

React16每次循环都会调用shouldYield判断当前是否有剩余时间。当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟 DOM 打上代表增/删/更新的标记,整个Scheduler与Reconciler的工作都在内存中进行(这个过程可能会由于有其他高优先级任务需要先更新、当前帧没有剩余时间等被中断,但由于都在内存中执行,不会更新页面DOM,所以反复中断也不会影响用户体验)。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer,Renderer根据Reconciler为虚拟 DOM 打的标记,同步执行对应的 DOM 操作。

参考

https://github.com/yanlele/node-index/tree/master/books/%E7%9F%A5%E8%AF%86%E5%BA%93/01%E3%80%81%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%E7%9F%A5%E8%AF%86/29.100%E4%B8%87%E4%B8%AA%E5%87%BD%E6%95%B0%E6%89%A7%E8%A1%8C%E4%BF%9D%E8%AF%81%E6%B5%8F%E8%A7%88%E5%99%A8%E4%B8%8D%E5%8D%A1

https://react.iamkasong.com/preparation/idea.html#cpu-%E7%9A%84%E7%93%B6%E9%A2%88