你是否曾经遇到过这样的场景:需要动态创建大量元素并添加到页面中,结果页面突然变得卡顿,甚至出现短暂的白屏?
为什么页面会卡顿?
当我们直接使用appendChild()
方法一个个添加元素时,每个操作都会触发浏览器的重绘(repaint)和回流(reflow)。这个过程就像是你去超市购物,每拿一件商品就去收银台结一次账,而不是把所有商品拿齐后一次性结账。
回流 是浏览器重新计算元素位置和几何结构的过程,重绘则是将新外观绘制到屏幕上的过程。这两个操作都非常消耗性能。
解决方案:DocumentFragment
DocumentFragment
就像一个"虚拟DOM容器",它允许我们在内存中构建DOM结构,然后一次性添加到实际DOM中。这样就只触发一次重绘和回流!
基本用法
ini
// 创建DocumentFragment
const fragment = document.createDocumentFragment();
// 批量创建元素并添加到fragment
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `列表项 ${i}`;
fragment.appendChild(li);
}
// 一次性添加到真实DOM
document.getElementById('myList').appendChild(fragment);
实战对比
传统方式(性能低下):
ini
const ul = document.getElementById('myList');
// 每个操作都会触发重绘/回流
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `列表项 ${i}`;
ul.appendChild(li); // 每次添加都触发重排
}
使用DocumentFragment(高性能):
ini
const ul = document.getElementById('myList');
const fragment = document.createDocumentFragment();
// 在内存中构建,不会触发重排
for (let i = 0; i < 1000; i++) {
const li = document.createElement('li');
li.textContent = `列表项 ${i}`;
fragment.appendChild(li);
}
// 一次性添加,只触发一次重排
ul.appendChild(fragment);
性能测试
我做了个简单测试,添加10000个列表项:
- 直接添加:约1200ms,页面明显卡顿
- 使用DocumentFragment:约80ms,页面流畅
性能提升超过10倍!
实际应用场景
- 无限滚动列表:加载大量数据时特别有用
- 动态表格:需要一次性添加多行数据
- 批量操作:需要对多个元素进行复杂操作后再添加到DOM
- 模板渲染:先构建完整结构再一次性插入
注意事项
- DocumentFragment本身不会被渲染,只有其内容会被添加
- 添加到真实DOM后,DocumentFragment会变为空
- 现代浏览器已普遍支持,兼容性良好