对于大量数据渲染的时候,JS运算并不是性能的瓶颈,性能的瓶颈主要在于渲染阶段。js执行要比dom渲染快的多。
时间分片
requestAnimationFrame
requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。
// 需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false;
}
// 每页多少条
let pageCount = Math.min(curTotal, once);
window.requestAnimationFrame(function() {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerText = curIndex + i;
ul.appendChild(li);
}
loop(curTotal - pageCount, curIndex + pageCount);
})
}
loop(total, index);
使用DocumentFragment优化
被作为一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式的html片段。DocumentFragment不是真实DOM树的一部分,它的变化不会触发DOM树的重新渲染,且不会导致性能等问题。DocumentFragment是DOM节点,但并不是DOM树的一部分,可以认为是存在内存中的,所以将子元素插入到文档片段时不会引起页面回流。
// 需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
// 每条记录的索引
let index = 0;
// 循环加载数据
function loop(curTotal, curIndex) {
if (curTotal <= 0) {
return false;
}
// 每页多少条
let pageCount = Math.min(curTotal , once);
window.requestAnimationFrame(function() {
let fragment = document.createDocumentFragment();
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerText = curIndex + i;
fragment.appendChild(li);
}
ul.appendChild(fragment);
loop(curTotal - pageCount, curIndex + pageCount);
})
}
loop(total, index);
虚拟列表
虚拟列表其实是按需显示的一种实现,只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。假设有1万条记录需要同时渲染,我们屏幕的可见区域的高度为500px,而列表项的高度为50px,则此时我们在屏幕中最多只能看到10个列表项,那么在首次渲染的时候,我们只需加载10条即可。当滚动发生时,可以通过计算当前滚动值得知此时在屏幕可见区域应该显示的列表项。假设滚动条距顶部的位置为150px,则我们可得知在可见区域内的列表项为第4项至第13项。
- 计算当前可视区域起始数据索引。
- 计算当前可视区域结束数据索引。
- 计算当前可视区域的数据,并渲染到页面中。
- 计算startIndex对应的数据在整个列表中的偏移位置并设置到列表上。
- 监听滚动列表的scroll事件,获取滚动位置scrollTop,从而重新计算可视区域的起始数据索引、结束数据索引、可视区域的数据、以及整个列表的偏移位置。