一次性渲染数据的优化
当遇到需要一次性向页面插入大量数据的情况,如何才能在不卡页面的情况下渲染数据,一般有两种做法:
1、时间分片;2、虚拟列表
这里只介绍如何使用时间分片来渲染大量数据:
简单粗暴
做法是 一次性渲染
<ul id ="container"></ul>
<script>
// 记录任务开始时间
let now = Date.now();
// 插入十万条数据
const total = 100000;
// 获取容器
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运行时间:187
// print: 总运行时间:2844
</script>
第一个 console.log的触发时间是在页面进行渲染之前,此时得到的间隔时间为JS运行所需要的时间
第二个 console.log是放到 setTimeout 中的,它的触发时间是在渲染完成才执行的
依照两次 console.log的结果,可以得出结论:
对于大量数据渲染的时候,JS运算并不是性能的瓶颈,性能的瓶颈主要在于渲染阶段
使用定时器优化
页面的卡顿是由于同时渲染大量DOM所引起的,所以我们考虑将渲染过程分批进行
在这里,我们使用 setTimeout来实现分批渲染:
<ul id="container"></ul>
<script>
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
let index = 0;
//循环加载数据
function loop(curTotal,curIndex){
if(curTotal <= 0){
return false;
}
//每页多少条
let pageCount = Math.min(curTotal , once);
setTimeout(()=>{
for(let i = 0; i < pageCount; i++){
let li = document.createElement('li');
li.innerText = curIndex + i+' : '+~~(Math.random()* total)
ul.appendChild(li)
}
loop(curTotal - pageCount,curIndex + pageCount)
},0)}
loop(total,index);
</script>
这种方法页面加载的时间变快了,每次刷新时可以很快的看到第一屏的所有数据,但是当我们快速滚动页面的时候,会发现页面出现闪屏或白屏的现象
使用 requestAnimationFrame
与 setTimeout相比, requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。
requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。
我们使用 requestAnimationFrame来进行分批渲染:
<ul id="container"></ul>
<script>
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
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 +' : '+~~(Math.random()* total)
ul.appendChild(li)
}loop(curTotal - pageCount,curIndex +pageCount)
})}
loop(total,index);
</script>
页面加载的速度很快,并且滚动的时候,也很流畅没有出现闪烁丢帧的现象。
使用 DocumentFragment
DocumentFragments是DOM节点,但并不是DOM树的一部分,可以认为是存在内存中的,所以将子元素插入到文档片段时不会引起页面回流。
当 append元素到 document中时,被 append进去的元素的样式表的计算是同步发生的,此时调用 getComputedStyle 可以得到样式的计算值。而 append元素到 documentFragment 中时,是不会计算元素的样式表,所以 documentFragment 性能更优。当然现在浏览器的优化已经做的很好了,
当 append元素到 document中后,没有访问 getComputedStyle 之类的方法时,现代浏览器也可以把样式表的计算推迟到脚本执行之后。
最后修改代码如下:
<ul id="container"></ul>
<script>
//需要插入的容器
let ul = document.getElementById('container');
// 插入十万条数据
let total = 100000;
// 一次插入 20 条
let once = 20;
//总页数
let page = total/once
//每条记录的索引
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+' : '+~~(Math.random()* total)
fragment.appendChild(li)
}
ul.appendChild(fragment)
loop(curTotal - pageCount,curIndex + pageCount)
})}
loop(total,index);
</script>