DocumentFragment可避免多次重排,因其不挂载于真实DOM,批量操作后仅一次插入触发单次布局;而直接循环appendChild会因每次操作触发样式计算、布局与绘制,造成性能损耗。

为什么直接循环 appendChild 会触发多次重排
每次对真实 DOM 节点调用 appendChild,浏览器都可能触发样式计算、布局(reflow)和绘制(paint),尤其当父容器已挂载、且子元素有尺寸/位置依赖时。哪怕只是连续追加 10 个 div,也可能造成 10 次重排 —— 这不是理论,getComputedStyle 或 DevTools 的 Rendering 面板能直接观测到 Layout 帧突增。
document.createDocumentFragment 是怎么“省掉”重排的
DocumentFragment 是一个轻量级的文档片段,它不挂载在真实 DOM 树中,因此任何对它的操作(如 appendChild、insertBefore)都不会触发重排或重绘。你可以在它上面批量构建完整结构,最后只用一次 appendChild 把整个片段插入目标容器 —— 浏览器只需做一次布局计算。
实操建议:
- 始终用
const frag = document.createDocumentFragment()创建新实例,不要复用(它插入后自动清空) - 所有要插入的节点,先
frag.appendChild(node),而不是直接操作目标容器 - 最后只执行一次
container.appendChild(frag);此时frag自动清空,不能再读取其childNodes - 注意:fragment 本身没有
innerHTML,不能用字符串拼接赋值;必须用 DOM 方法逐个添加节点
对比:Fragment vs innerHTML vs createElement 循环
三者性能和语义差异明显:
-
innerHTML += htmlString:看似简洁,但每次赋值都会销毁并重建全部子节点,引发多次解析、重排,且丢失事件监听器和表单状态 - 纯
createElement+appendChild循环:每个appendChild都可能触发重排(尤其在已渲染容器中) -
DocumentFragment:节点构建阶段零重排,插入阶段仅一次布局,保留节点引用、事件绑定和表单输入值
示例场景:动态生成 50 行表格
const frag = document.createDocumentFragment();
for (let i = 0; i < 50; i++) {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${i}</td><td>data-${i}</td>`;
frag.appendChild(tr); // 不触发重排
}
tableBody.appendChild(frag); // 仅此处触发一次重排
容易被忽略的边界情况
Fragment 不是万能加速器,以下情况仍可能绕过优化效果:
- 在 fragment 中调用
getBoundingClientRect()或offsetHeight:这会强制浏览器同步计算布局,导致提前重排 - 插入前对 fragment 子节点反复读取样式(如
getComputedStyle(el).color),同样触发强制同步布局 - 目标容器本身处于 CSS 动画/transition 中,或设置了
will-change: transform等,可能让浏览器更激进地重排 - 使用
DocumentFragment后仍对每个子节点单独设置style.cssText,不如统一用 class 控制样式,避免内联样式批量重写
真正起效的前提是:构建过程完全脱离布局查询,所有样式通过 class 或一次性 style 属性设置,最后单次挂载。


















