前言
在工作中,有时会遇到需要一些不能使用分页方式来加载列表数据的业务情况,对于此,我们称这种列表叫做长列表。比如,在一些外汇交易系统中,前端会实时的展示用户的持仓情况(收益、亏损、手数等),此时对于用户的持仓列表一般是不能分页的。
在高性能渲染十万条数据(时间分片)一文中,提到了可以使用时间分片的方式来对长列表进行渲染,但这种方式更适用于列表项的DOM结构十分简单的情况。本文会介绍使用虚拟列表的方式,来同时加载大量数据。
为什么需要使用虚拟列表
假设我们的长列表需要展示10000条记录,我们同时将10000条记录渲染到页面中,先来看看需要花费多长时间:
button
document.getElementById('button').addEventListener('click',function(){//记录任务开始时间
let now =Date.now();//插入一万条数据
const total = 10000;//获取容器
let ul = document.getElementById('container');//将数据插入容器中
for (let i = 0; i < total; i++) {
let li= document.createElement('li');
li.innerText= ~~(Math.random() *total)
ul.appendChild(li);
}
console.log('JS运行时间:',Date.now() -now);
setTimeout(()=>{
console.log('总运行时间:',Date.now() -now);
},0)//print JS运行时间: 38
//print 总运行时间: 957
})
当我们点击按钮,会同时向页面中加入一万条记录,通过控制台的输出,我们可以粗略的统计到,JS的运行时间为38ms,但渲染完成后的总时间为957ms。
简单说明一下,为何两次console.log的结果时间差异巨大,并且是如何简单来统计JS运行时间和总渲染时间:
在 JS 的Event Loop中,当JS引擎所管理的执行栈中的事件以及所有微任务事件全部执行完后,才会触发渲染线程对页面进行渲染
第一个console.log的触发时间是在页面进行渲染之前,此时得到的间隔时间为JS运行所需要的时间
第二个console.log是放到 setTimeout 中的,它的触发时间是在渲染完成,在下一次Event Loop中执行的
关于Event Loop的详细内容请参见这篇文章-->
然后,我们通过Chrome的Performance工具来详细的分析这段代码的性能瓶颈在哪里:
从Performance可以看出,代码从执行到渲染结束,共消耗了960.8ms,其中的主要时间消耗如下:
Event(click) : 40.84ms
Recalculate Style : 105.08ms
Layout : 731.56ms
Update Layer Tree : 58.87ms
Paint : 15.32ms
从这里我们可以看出,我们的代码的执行过程中,消耗时间最多的两个阶段是Recalculate Style和Layout。
Recalculate Style:样式计算,浏览器根据css选择器计算哪些元素应该应用哪些规则,确定每个元素具体的样式。
Layout:布局,知道元素应用哪些规则之后,浏览器开始计算它要占据的空间大小及其在屏幕的位置。
在实际的工作中,列表项必然不会像例子中仅仅只由一个li标签组成,必然是由复杂DOM节点组成的。
那么可以想象的是,当列表项数过多并且列表项结构复杂的时候,同时渲染时,会在Recalculate Style和Layout阶段消耗大量的时间。
而虚拟列表就是解决这一问题的一种实现。
什么是虚拟列表
虚拟列表其实是按需