前端十万条数据下 JS 高性能渲染解决方案
笨办法:一次性渲染
const total = 100000;
const ul = document.getElementById('ul');
for (let index = 0; index < total; index++) {
const li = document.createElement('li');
li.innerHTML = index + 1;
ul.appendChild(li);
}
缺点:引起大量重排(reflow)。由于是同步任务会阻塞页面渲染。页面会出现一段时间的空白。
使用 setTimeout 分批加载
const total = 100000;
const once = 2000;
const index = 0;
const ul = document.getElementById('ul');
function insert(total2, index2) {
if (total2 <= 0) { return }
setTimeout(() => {
for (let i = 0; i < once; i++) {
const li = document.createElement('li');
li.innerHTML = index2 + i + 1;
ul.appendChild(li);
}
insert(total2 - once, index2 + once)
})
}
insert(total, index)
这个方案解决了页面空白的问题。虽然用户体验提升了,但是重排没有减少。
使用 requestAnimationFrame 替代 setTimeout
可以使用 requestAnimationFrame 替代 setTimeout。
const total = 100000;
const once = 2000;
const index = 0;
const ul = document.getElementById('ul');
function insert(total2, index2) {
if (total2 <= 0) { return }
window.requestAnimationFrame(() => {
for (let i = 0; i < once; i++) {
const li = document.createElement('li');
li.innerHTML = index2 + i + 1;
ul.appendChild(li);
}
insert(total2 - once, index2 + once)
})
}
insert(total, index)
使用 createDocumentFragment
增加操作 fragment、减少操作 DOM 可以减少重排。
const total = 100000;
const once = 2000;
const index = 0;
const ul = document.getElementById('ul');
function insert(total2, index2) {
if (total2 <= 0) { return }
window.requestAnimationFrame(() => {
const fragment = document.createDocumentFragment();
for (let i = 0; i < once; i++) {
const li = document.createElement('li');
li.innerHTML = index2 + i + 1;
fragment.appendChild(li);
}
ul.appendChild(fragment);
insert(total2 - once, index2 + once)
})
}
insert(total, index)
总结
JS 是阻塞加载,会阻塞 DOM 树或阻塞渲染树的构建。
setTimeout 的执行时间并不是确定的。
requestAnimationFrame 优于 setTimeout。
重排必定会引发重绘,但重绘不一定会引发重排。
Document.createDocumentFragment 可以用于减少重排。