高效 DOM 操作

减少重排重绘、批量更新、事件委托等 DOM 优化技巧

问题

如何高效操作 DOM?

解答

1. 减少 DOM 访问次数

// ❌ 差:多次访问 DOM
for (let i = 0; i < 100; i++) {
  document.getElementById('list').innerHTML += `<li>${i}</li>`;
}

// ✅ 好:缓存 DOM 引用,一次性更新
const list = document.getElementById('list');
let html = '';
for (let i = 0; i < 100; i++) {
  html += `<li>${i}</li>`;
}
list.innerHTML = html;

2. 使用 DocumentFragment

// 创建文档片段,避免多次重排
const fragment = document.createDocumentFragment();

for (let i = 0; i < 1000; i++) {
  const li = document.createElement('li');
  li.textContent = `Item ${i}`;
  fragment.appendChild(li); // 操作在内存中进行
}

// 只触发一次重排
document.getElementById('list').appendChild(fragment);

3. 批量修改样式

// ❌ 差:多次修改样式,触发多次重排
const el = document.getElementById('box');
el.style.width = '100px';
el.style.height = '100px';
el.style.margin = '10px';

// ✅ 好:使用 cssText 或 class 一次性修改
el.style.cssText = 'width: 100px; height: 100px; margin: 10px;';

// 或者切换 class
el.className = 'box-style';

4. 离线操作 DOM

// 将元素脱离文档流后再操作
const list = document.getElementById('list');

// 方法1:隐藏元素
list.style.display = 'none';
// 进行多次 DOM 操作...
for (let i = 0; i < 100; i++) {
  const li = document.createElement('li');
  list.appendChild(li);
}
list.style.display = 'c9s3v';

// 方法2:克隆节点
const clone = list.cloneNode(true);
// 在克隆节点上操作...
list.parentNode.replaceChild(clone, list);

5. 避免强制同步布局

// ❌ 差:读写交替,强制同步布局
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
  const width = box.offsetWidth; // 读
  box.style.width = width + 10 + 'px'; // 写
});

// ✅ 好:先批量读,再批量写
const widths = [];
boxes.forEach(box => {
  widths.push(box.offsetWidth); // 批量读
});
boxes.forEach((box, i) => {
  box.style.width = widths[i] + 10 + 'px'; // 批量写
});

6. 事件委托

// ❌ 差:给每个元素绑定事件
document.querySelectorAll('li').forEach(li => {
  li.addEventListener('click', handleClick);
});

// ✅ 好:利用事件冒泡,在父元素上监听
document.getElementById('list').addEventListener('click', (e) => {
  if (e.target.tagName === 'LI') {
    handleClick(e);
  }
});

7. 使用 requestAnimationFrame

// 将 DOM 操作放到下一帧执行,避免阻塞
function updateDOM() {
  requestAnimationFrame(() => {
    // DOM 操作在浏览器下次重绘前执行
    element.style.transform = `translateX(${position}px)`;
  });
}

关键点

  • 减少访问次数:缓存 DOM 引用,避免重复查询
  • 批量操作:使用 DocumentFragment 或字符串拼接,一次性插入
  • 避免强制同步布局:读写分离,先批量读取再批量写入
  • 事件委托:利用冒泡机制,减少事件监听器数量
  • requestAnimationFrame:动画和频繁更新使用 rAF 优化