笨办法:一次性渲染

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 可以用于减少重排。