背景
偶然看到一篇文章,《前端大容量缓存方案-IndexedDB》-zhuanlan.zhihu.com/p/104536473 在了解IndexedDB过程中,发现了另外一篇文章zhuanlan.zhihu.com/p/95076534 在大佬的主页发现一篇阿里的面试坎坷之路,然后看到了相关的面试知识点www.zhihu.com/column/c_10... 其中讲到了很多知识点,觉得似曾相识,又说不出所以然,于是决定重新学习一遍
首先注意到了"时间切片",因为我们项目里目前也有因响应时间长,页面白屏,影响体验的问题
在了解时间切片的过程中,发现一篇文章zhuanlan.zhihu.com/p/111263128 文章底部,"高性能渲染十万条数据(时间分片)"---juejin.cn/post/684490... 发现了大佬。那么,就倒着学习吧。
菜鸟进阶
1.关于Event Loop、宏任务、微任务
CPU

进程

进程类似工厂的车间,进程之间相互独立,任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
线程

线程好比车间里的工人,一个进程可以包括多个线程,多个线程共享进程资源。
浏览器是多进程的,一个tab页对应一个进程。
浏览器包含的进程
- 主进程
- 第三方插件进程(每种类型的插件对应一个进程,使用时才会创建)
- GPU进程(用于3D绘制)
- 渲染进程,及浏览器内核(负责页面渲染,脚本执行,事件处理)
渲染进程包含的线程
-
GUI渲染线程
- 负责渲染页面,布局和绘制
- 页面需要重绘和回流时,该线程就会执行
- 与js引擎线程互斥,防止渲染结果不可预期
-
JS引擎线程
- 负责处理解析和执行javascript脚本程序
- 只有一个JS引擎线程(单线程)
- 与GUI渲染线程互斥,防止渲染结果不可预期(一个渲染,一个改,要是同时进行,岂不是矛盾了)
-
事件触发线程
- 用来控制事件循环(鼠标点击、setTimeout、ajax等)
- 当事件满足触发条件时,将事件放入到JS引擎所在的执行队列中
-
定时触发器线程
- setInterval与setTimeout所在的线程
- 定时任务并不是由JS引擎计时的,是由定时触发线程来计时的
- 计时完毕后,通知事件触发线程
-
异步http请求线程
- 浏览器有一个单独的线程用于处理AJAX请求
- 当请求完成时,若有回调函数,通知事件触发线程
Event Loop
不管是setTimeout/setInterval
和XHR/fetch
代码,在这些代码执行时, 本身是同步任务,而其中的回调函数才是异步任务。
宏任务 浏览器为了能够使宏任务
和DOM任务
有序的进行,会在一个宏任务
执行结果后,在下一个宏任务
执行前,GUI渲染线程
开始工作,对页面进行渲染。
主代码块,setTimeout,setInterval等,都属于宏任
务
微任务
宏任务结束后,会执行渲染,然后执行下一个宏任务,而微任务可以理解成在当前宏任务执行后立即执行的任务
当宏任务执行完,会在渲染前,将执行期间所产生的所有微任务都执行完。
Promise, process.nextTick,属于微任务。
ini
`document.body.style = 'background:blue'
console.log(1);
Promise.resolve().then(()=>{
console.log(2);
document.body.style = 'background:black'
});
console.log(3);`
控制台输出1,3,2,页面的背景色直接变成黑色,没有经过蓝色的阶段,因为执行完微任务才会渲染:
宏任务->微任务->渲染
javascript
setTimeout(() => {
console.log(1)
Promise.resolve(3).then(data => console.log(data))
}, 0)
setTimeout(() => { console.log(2) }, 0) // print : 1 3 2`
除主代码块外,共有两个宏任务,其中第一个宏任务执行中,输出1,并且创建了微任务队列,所以在下一个宏任务队列执行前,先执行微任务,微任务执行中,输出3,微任务执行后,执行下一次宏任务,执行总输出2。
总结:
先执行一个宏任务,执行过程中,有微任务,则先添加到微任务队列,继续执行当前宏任务,执行完宏任务后,执行所有微任务队列,然后渲染,最后开启下一次宏任务。

链接地址: juejin.cn/post/684490...
2.高性能渲染十万条数据(时间分片)

运行结果如下图:

看完第一部分的Event Loop就不难理解这块了,第一个console打印的是第一次宏任务执行的时间,第二个console打印的是第二个宏任务执行的结束时间,中间的时间差就是渲染的时间。
因此我们知道对于大量数据渲染的时候,JS运算不是性能瓶颈,性能瓶颈在于渲染阶段。
优化
方案1: 使用setTimeout分批渲染,明显渲染速度有提升,但是滚动时会出现白屏现象。
该方案的缺点
(1)setTimeout的执行时间并不是设置的时间,只有主线程执行完,才会去检查事件队列中的任务是否需要执行,实际执行事件可能会比其设定的时间晚。
(2)刷新频率受屏幕分辨率和屏幕尺寸的影响,不同设备的刷新频率可能会不同,而setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
方案2:使用requestAnimationFrame
与setTimeout相比,requestAnimationFrame最大的优势是由系统决定回调函数的执行时机。如果屏幕刷新频率是60Hz,那么回调函数就每16.7ms执行一次,如果刷新频率是75Hz,那么这个间隔时间就变成了1000/75=13.3ms。

页面加载速度很快,并且滚动的时候,没有出现闪烁丢帧的现象。
经过以上学习,我对时间分片的理解是,对于大量渲染的任务,性能卡在渲染上,那就把大的宏任务分成若干宏任务,缩短了每个宏任务的渲染时间,这个时间间隔如果和屏幕分辨率保持一致,在视觉上就不会出现白屏卡顿的现象。