DocumentFragment 是离线轻量容器,不触发重排;将多次 DOM 操作合并为一次插入时的布局计算,从而减少重排。它不支持 querySelector 和直接事件绑定,但比 innerHTML 更安全,且在 IE9+ 原生支持。

DocumentFragment 是什么,为什么它能减少重排
DocumentFragment 不是真实 DOM 树的一部分,它是个离线的轻量容器,往里面 appendChild 任何节点都不会触发浏览器渲染流程。很多人误以为“快”是因为 JS 执行快,其实关键在于:它把原本 100 次可能触发重排的操作,压缩成 1 次真实插入时的布局计算。
重排(reflow)发生在元素几何属性变化时,比如宽高、位置、字体大小等;而 DocumentFragment 完全绕过这个阶段——直到你调用 container.appendChild(fragment) 那一刻,浏览器才开始计算整个 fragment 子树的布局。
- fragment 插入后自动清空,不可复用;需多批操作就每次新建一个
- 它不支持
querySelector或事件监听器直接绑定(因为没挂载),但可配合事件委托使用 - 比字符串拼接
innerHTML更安全:不会丢失已有事件监听器,也无 XSS 风险(前提是不用innerHTML注入未过滤内容)
怎么写才不踩坑:常见错误写法对比
下面这些写法看着差不多,但性能和行为差异极大:
❌ 错误:在循环里反复操作真实 DOM
立即学习“前端免费学习笔记(深入)”;
const container = document.getElementById('list');
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
container.appendChild(li); // 每次都触发潜在重排!
}
❌ 危险:用 innerHTML += 拼接
el.innerHTML += '<li>new item</li>'; // 浏览器会先序列化全部现有子节点 → 拼接 → 全量重建,100 行变 O(n²) 开销
✅ 正确:用 DocumentFragment 批量组装
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const li = document.createElement('li');
li.textContent = `Item ${i}`;
fragment.appendChild(li); // 纯内存操作,零渲染代价
}
container.appendChild(fragment); // 仅此处触发一次重排
什么时候该换别的方案?DocumentFragment 的边界
DocumentFragment 并非万能。以下场景它帮不上忙,甚至会适得其反:
- 只插入 1–2 个节点:开销反而略高于直插,没必要封装
- 需要保留对每个新节点的引用并后续单独操作(比如加动画 class、绑定不同事件):fragment 插入后节点就脱离 fragment,但引用仍有效;不过若逻辑复杂,不如先存数组再批量处理
- 更新已有 DOM 节点的样式或文本内容(非新增):这时应避免循环中混用读写,优先用
el.className = 'xxx'或el.style.cssText = '...'合并写入 - 渲染上万行数据:即使用了 fragment,一次性插入仍会让主线程卡顿 >100ms。必须配合
requestIdleCallback或requestAnimationFrame分帧,每帧处理 20 个左右
兼容性和 fallback 注意点
DocumentFragment 在所有现代浏览器和 IE9+ 都原生支持,无需 polyfill。但如果你的项目仍需支持 IE8 及更早版本,document.createDocumentFragment() 会返回 undefined,此时可 fallback 到临时容器技巧:
const frag = document.createDocumentFragment ?
document.createDocumentFragment() :
document.createElement('div');
注意:IE8 下用 div 替代时,记得最后用 frag.innerHTML 清空内容,否则可能残留;且不能依赖 frag.childNodes.length 判断是否为空——IE8 中它始终为 0。
真正容易被忽略的是:DOM 操作的延迟感知不在 JS 引擎,而在浏览器渲染流水线。哪怕你优化到毫秒级,只要触发了强制同步布局(比如循环里读 offsetHeight 再写 style),整套优化就归零。



















