小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
大概是上个月刷到一道笔试题,让手写出,当渲染一万条数据时,前端应该怎么渲染到页面上,目前来看这道题的出现率还是很高的,主要是跟性能优化有关,这里具体讲一下
啊那现在我们先渲染一万条出来,分批处理,并利用setTimeout把逻辑推到宏任务队列中
<div>
<ul class="list"></ul>
</div>
<script>
const total = 10000,
// 每次处理20条
each = 20,
// 需要处理次数
needTimes = Math.ceil(total / each),
content = document.querySelector('.list')
// 当前处理次数
let currentTime = 0
function add() {
setTimeout(() =>{
// 分批处理
for (let i = 0; i < each; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(i+currentTime*each)
content.appendChild(li)
}
currentTime++;
// 循环处理
if (currentTime < needTimes) add();
},1000/60)
}
add();
</script>
这样只是能渲染出来,但因为每次循环都会操作一次dom,造成性能上会差点,测试页面Load为190ms
这里利用 createdocumentfragment() 修改一下
createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法
通过循环把每次分批处理的节点添加到这个节点对象中,然后添加到dom中,这样每次进入方法,每次进入方法后重排的次数就从each次变为了1次
这里是为了减少操作dom的次数
<div>
<ul class="list"></ul>
</div>
<script>
const total = 10000,
// 每次处理20条
each = 20,
// 需要处理次数
needTimes = Math.ceil(total / each),
content = document.querySelector('.list')
// 当前处理次数
let currentTime = 0
function add() {
setTimeout(() =>{
// 创建一个虚拟的节点对象
const fragment = document.createDocumentFragment()
// 分批处理
for (let i = 0; i < each; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(i+currentTime*each)
fragment.appendChild(li)
}
把虚拟的节点对象添加到主节点中
content.appendChild(fragment)
currentTime++;
// 循环处理
if (currentTime < needTimes) add();
},1000/60)
}
add();
</script>
这里测试Load只有90ms了
但这里还不是最优的的方案,setTimeout这里其实是属于写死了的,并且还会受任务队列的影响,这样不利于这样高频次的刷新,于是需要修改一下
更换成window.requestAnimationFrame() 会更优,为什么
- 因为回调函数执行次数通常与浏览器屏幕刷新次数相匹配,不会引起丢帧
- 窗口没激活时,动画将停止,省计算资源
- 作为浏览器专门为处理动画提供的api,经过浏览器优化,动画更流畅
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame()
兼容:IE10以上
<div>
<ul class="list"></ul>
</div>
<script>
const total = 10000,
// 每次处理20条
each = 20,
// 需要处理次数
needTimes = Math.ceil(total / each),
content = document.querySelector('.list')
// 当前处理次数
let currentTime = 0
function add() {
// 创建一个虚拟的节点对象
const fragment = document.createDocumentFragment()
// 分批处理
for (let i = 0; i < each; i++) {
const li = document.createElement('li')
li.innerText = Math.floor(i+currentTime*each)
fragment.appendChild(li)
}
把虚拟的节点对象添加到主节点中
content.appendChild(fragment)
currentTime++;
// 循环处理
if (currentTime < needTimes) window.requestAnimationFrame(add);
}
window.requestAnimationFrame(add)
</script>
测试Load降低到了最低30ms