用 DocumentFragment 批量插入节点最高效,因其离线组装不触发重排,仅最后挂载时触发一次;而循环 appendChild 或 innerHTML 会频繁重排,且后者还导致事件丢失。

直接用 document.createDocumentFragment(),别拼 innerHTML,也别在循环里反复调用 appendChild——这是减少重排最稳、最可控的方式。
为什么循环调用 appendChild 会卡顿
每次对已挂载的容器执行 appendChild,浏览器都可能触发一次重排(reflow),尤其当父元素有 CSS 宽高、浮动、定位或字体等依赖布局的样式时。100 次插入 ≠ 100 次 DOM 节点创建,而是最多 100 次样式计算 + 布局 + 绘制准备。
- 重排代价远高于重绘,它要重新计算整个受影响区域的盒模型
- 旧版浏览器(如 IE11)或复杂 CSS 下,这种开销更明显
- 即使节点内容极简(比如只写
textContent),只要父容器已渲染,就逃不开潜在重排
怎么用 DocumentFragment 正确批量插入
核心就三步:创建碎片 → 离线组装 → 一次性挂载。它不属主 DOM 树,所有操作完全静默,不触发任何渲染流程。
- 必须每次新建:
const fragment = document.createDocumentFragment(),不能复用已插入过的 fragment(它插入后自动清空) - 所有
createElement、appendChild都作用于fragment,不是真实容器 - 最后只调用一次真实 DOM 的
appendChild或replaceChildren(现代浏览器推荐后者) - 如果需保留事件委托,确保事件监听器绑定在父容器上,而非每个子节点内联绑定
示例:
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
立即学习“前端免费学习笔记(深入)”;
const ul = document.querySelector('ul');
const fragment = document.createDocumentFragment();
['Apple', 'Banana', 'Cherry'].forEach(text => {
const li = document.createElement('li');
li.textContent = text;
fragment.appendChild(li); // ✅ 不触发重排
});
ul.appendChild(fragment); // ✅ 仅此处触发一次重排
innerHTML 追加 vs DocumentFragment:选哪个
innerHTML += 看似简单,实则危险:它会强制浏览器先序列化当前子树 → 拼接字符串 → 全量解析重建整个子树。哪怕只加一个 <tr>,已有全部 <tr> 都会被销毁再重建,事件监听器全丢。
- 若必须用字符串拼接,改用
insertAdjacentHTML('beforeend', htmlString),它只追加、不重建 - 若 HTML 来自可信源且结构完整(如整张表),可提前 escape 后用
innerHTML,但 XSS 风险需自行兜底 -
DocumentFragment更安全、更可控,能保留节点引用、支持动态属性/事件绑定,是复杂场景首选
批量更新已有节点时的隐藏陷阱
不是只有“插入新节点”才重排。循环中修改已有元素的 style 属性,比如交替读取 offsetHeight 和设置 style.width,会引发布局抖动(Layout Thrashing)——浏览器被迫同步 flush 渲染队列,反复重排。
- 读写必须分离:先批量读取所有需要的尺寸(
offsetHeight、getBoundingClientRect()),再批量写入样式 - 优先用
el.className = 'active'或el.classList.toggle()替代手动拼className字符串 - 动态计算值时,避免在循环中混用
el.style.xxx = ...和el.offsetHeight - 若需动画或高频更新,把写操作包进
requestAnimationFrame,但注意它不解决读取导致的强制同步
真正容易被忽略的,是“读-写分离”这个前提——很多人以为用了 DocumentFragment 就万事大吉,结果在组装前反复读取布局属性,照样白搭。


















