-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
如何高性能的渲染十万条数据(时间分片) #15
Comments
求教 在这个案例里,使用 fragment 相比在 for 循环里 使用 fragment 是渲染一次,在 for 里循环 |
@wangjianio 我记得每次 appendChild 都会渲染一次,而加到 fragment 上在 appendChild 的话只渲染一次。 |
@wangjianio |
@chenqf 👍受教了。 还是在 for 循环的情况下。
不过我简单测试了几个案例,发现 Chrome 下优化的很好,fragment 和 |
👍 |
前言
在实际工作中,我们很少会遇到一次性需要向页面中插入大量数据的情况,但是为了丰富我们的知识体系,我们有必要了解并清楚当遇到大量数据时,如何才能在不卡主页面的情况下渲染数据,以及其中背后的原理。
对于一次性插入大量数据的情况,一般有两种做法:
本文作为开篇,着重来介绍如何使用
时间分片
的方式来渲染大量数据,虚拟列表相关的内容,日后会持续整理。最粗暴的做法(一次性渲染)
我们先来看看最粗暴的做法,一次性将大量数据插入到页面中:
我们对十万条记录进行循环操作,JS的运行时间为
187ms
,还是蛮快的,但是最终渲染完成后的总时间确是2844ms
。简单说明一下,为何两次
console.log
的结果时间差异巨大,并且是如何简单来统计JS运行时间
和总渲染时间
:Event Loop
中,当JS引擎所管理的执行栈中的事件以及所有微任务事件全部执行完后,才会触发渲染线程对页面进行渲染console.log
的触发时间是在页面进行渲染之前,此时得到的间隔时间为JS运行所需要的时间console.log
是放到 setTimeout 中的,它的触发时间是在渲染完成,在下一次Event Loop
中执行的关于Event Loop的详细内容请参见这篇文章-->
依照两次
console.log
的结果,可以得出结论:对于大量数据渲染的时候,JS运算并不是性能的瓶颈,性能的瓶颈主要在于渲染阶段
使用定时器
从上面的例子,我们已经知道,页面的卡顿是由于同时渲染大量DOM所引起的,所以我们考虑将渲染过程分批进行
在这里,我们使用
setTimeout
来实现分批渲染用一个gif图来看一下效果
我们可以看到,页面加载的时间已经非常快了,每次刷新时可以很快的看到第一屏的所有数据,但是当我们快速滚动页面的时候,会发现页面出现闪屏或白屏的现象
为什么会出现闪屏现象呢
首先,理清一些概念。
FPS
表示的是每秒钟画面更新次数。我们平时所看到的连续画面都是由一幅幅静止画面组成的,每幅画面称为一帧
,FPS
是描述帧
变化速度的物理量。大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次,
FPS
为60frame/s,为这个值的设定受屏幕分辨率、屏幕尺寸和显卡的影响。因此,当你对着电脑屏幕什么也不做的情况下,大多显示器也会以每秒60次的频率正在不断的更新屏幕上的图像。
为什么你感觉不到这个变化?
那是因为人的眼睛有视觉停留效应,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,
这中间只间隔了16.7ms(1000/60≈16.7),所以会让你误以为屏幕上的图像是静止不动的。
而屏幕给你的这种感觉是对的,试想一下,如果刷新频率变成1次/秒,屏幕上的图像就会出现严重的闪烁,
这样就很容易引起眼睛疲劳、酸痛和头晕目眩等症状。
大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。
因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms。
直观感受,不同帧率的体验:
简单聊一下 setTimeout 和闪屏现象
setTimeout
的执行时间并不是确定的。在JS中,setTimeout
任务被放进事件队列中,只有主线程执行完才会去检查事件队列中的任务是否需要执行,因此setTimeout
的实际执行时间可能会比其设定的时间晚一些。setTimeout
只能设置一个固定时间间隔,这个时间不一定和屏幕的刷新时间相同。以上两种情况都会导致setTimeout的执行步调和屏幕的刷新步调不一致。
在
setTimeout
中对dom进行操作,必须要等到屏幕下次绘制时才能更新到屏幕上,如果两者步调不一致,就可能导致中间某一帧的操作被跨越过去,而直接更新下一帧的元素,从而导致丢帧现象。使用 requestAnimationFrame
与
setTimeout
相比,requestAnimationFrame
最大的优势是由系统来决定回调函数的执行时机。如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,
requestAnimationFrame
的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。我们使用
requestAnimationFrame
来进行分批渲染:看下效果
我们可以看到,页面加载的速度很快,并且滚动的时候,也很流畅没有出现闪烁丢帧的现象。
这就结束了么,还可以再优化么?
当然~~
使用 DocumentFragment
先解释一下什么是 DocumentFragment ,文献引用自MDN
从MDN的说明中,我们得知
DocumentFragments
是DOM节点,但并不是DOM树的一部分,可以认为是存在内存中的,所以将子元素插入到文档片段时不会引起页面回流。当
append
元素到document
中时,被append
进去的元素的样式表的计算是同步发生的,此时调用 getComputedStyle 可以得到样式的计算值。而
append
元素到documentFragment
中时,是不会计算元素的样式表,所以documentFragment
性能更优。当然现在浏览器的优化已经做的很好了,当
append
元素到document
中后,没有访问 getComputedStyle 之类的方法时,现代浏览器也可以把样式表的计算推迟到脚本执行之后。最后修改代码如下:
参考
写在最后
点赞
和关注
Watch
&Star ★
The text was updated successfully, but these errors were encountered: