1. 这道题在考什么?
对于性能优化的处理方案
对于前端渲染机制的了解
极端情况下的处理及知识领域的广度
常规处理
const renderList = async () => {
console.time('列表时间')
const list = await getList();
list.forEach( item => {
const div = document.createElement('div')
div.className = 'flex'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
container.appendChild(div)
});
console.timeEnd('列表时间')
}
renderList()
这种方案就是简单粗暴的循环渲染
此方案耗时大概是 13s
这种做法当然是不可取的,等到天都黑了,估计都出不来
2. 优化的第一种方式—— 前端分页
const renderList = async () => {
console.time('列表时间')
const list = await getList();
const total = list.length;
const page = 0;
const limit = 200;
// 总页数
const totalPage = Math.ceil(total / limit); // Math.ceil 1.1 => 2
const render = (page) => {
if(page >= totalPage) return
//写一个定时器
setTimeout(() => {
for(let i = page * limit; i < page * limit + limit; i++){
const item = list[i];
const div = document.createElement('div')
div.className = 'flex'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
container.appendChild(div)
}
render(page + 1)
}, 0)
}
render(page);
console.timeEnd('列表时间')
}
renderList()
思路是把十万条数据分成 10w / 200页,再加上setTimeout,每次渲染一页,速度得到了大幅度提升。
方案耗时:不到 1s 搞定
再次进行优化使用 requestAnimationFrame
window.requestAnimationFrame()
告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。
该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行;
若你想在浏览器下次重绘之前继续更新下一帧动画,
那么回调函数自身必须再次调用window.requestAnimationFrame()
const renderList = async () => {
console.time('列表时间')
const list = await getList();
const total = list.length;
const page = 0;
const limit = 200;
// 总页数
const totalPage = Math.ceil(total / limit); // Math.ceil 1.1 => 2
const render = (page) => {
if(page >= totalPage) return
requestAnimationFrame(() => {
for(let i = page * limit; i < page * limit + limit; i++){
const item = list[i];
const div = document.createElement('div')
div.className = 'flex'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
container.appendChild(div)
}
render(page + 1)
})
}
render(page);
console.timeEnd('列表时间')
}
renderList()
使用 requestAnimationFrame 代替 setTimeout,减少了重排的次数,极大提高了性能
最佳方案使用文档碎片 document.createDocumentFragment()
DocumentFragments —— 文档碎片
DocumentFragments 是DOM节点。它们不是主DOM树的一部分。
通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。
在DOM树中,文档片段被其所有的子元素所代替。因为文档片段存在于内存中,并不在DOM树中。
所以将子元素插入到文档片段时不会引起【页面回流】(对元素位置和几何上的计算)。
因此,使用文档片段通常会带来更好的性能。
const renderList = async () => {
console.time('列表时间')
const list = await getList();
const total = list.length;
const page = 0;
const limit = 200;
// 总页数
const totalPage = Math.ceil(total / limit); // Math.ceil 1.1 => 2
const render = (page) => {
if(page >= totalPage) return
requestAnimationFrame(() => {
const fragment = document.createDocumentFragment()
// 文档碎片 => dom节点 不是在dom树上一部分
// N次追加 => 1次
for(let i = page * limit; i < page * limit + limit; i++){
const item = list[i];
const div = document.createElement('div')
div.className = 'flex'
div.innerHTML = `<img src="${item.src}" /><span>${item.text}</span>`
fragment.appendChild(div)
// container.appendChild(div)
}
container.appendChild(fragment)
render(page + 1)
})
}
render(page);
console.timeEnd('列表时间')
}
renderList()
这里的优化点主要是:之前是创建一个div就追加一次:
div.innerHTML = <img src="${item.src}" /><span>${item.text}</span>
container.appendChild(div)
现在改成一次性追加,极大的提高了性能。
container.appendChild(fragment)