直接循环appendchild会卡顿,因为每次操作都触发重排和重绘;浏览器需反复计算布局并重绘,尤其在可见dom频繁增删时,性能开销剧增。

为什么直接循环 appendChild 会卡顿
浏览器在每次 DOM 变更后都可能触发重排(reflow)和重绘(repaint),尤其当循环中反复操作真实节点时,比如:
for (let i = 0; i 即使 <code>container</code> 是空的,浏览器仍可能为每个新增节点做样式计算、几何测量。100 次就是 100 次潜在开销。 <h3> <code>document.createDocumentFragment</code> 的正确用法</h3> <p>它本质是一个轻量级、不挂载到文档树的“DOM 容器”,所有对它的操作不会触发渲染。关键点在于:必须先完成全部子节点添加,再一次性挂载到真实 DOM。</p> <p>常见错误写法:</p><pre class="brush:php;toolbar:false;">const frag = document.createDocumentFragment(); for (let i = 0; i <p>正确做法:</p>
- 只创建一次
DocumentFragment - 所有
appendChild都作用于该 fragment - 循环结束后,仅调用一次
container.appendChild(frag)
fragment 和数组拼接字符串的性能差异在哪
有人会想:我用 strings.push('<div>' + i + '</div>') 再 container.innerHTML = strings.join('') 不也是一次性?确实快,但有隐患:
-
innerHTML会销毁原有子节点并重建整个结构,丢失绑定的事件监听器、表单状态、自定义属性值(如el.dataset.id) - 若内容含用户输入或富文本,需手动转义防止 XSS,而
createElement天然安全 - fragment 支持对每个节点单独设置属性、class、事件——这是纯字符串做不到的
所以选 fragment 不是为了“绝对最快”,而是为了可控、安全、可维护的批量插入。
现代替代方案:append 和 replaceChildren 能否绕过 fragment
可以,但要注意兼容性和语义:
-
container.append(...nodes)接收多个节点参数,等价于把它们依次加进 fragment 后再 append,内部已优化,Chrome/Firefox/Edge 均支持(IE 不行) -
container.replaceChildren(...nodes)更激进:清空旧内容并插入新节点,适合整页刷新式更新 - 二者都不需要手写 fragment,但若你要复用同一组节点多次(比如缓存后反复插入),还是得靠 fragment —— 因为 fragment 可被多次
appendChild,而普通节点移走就没了
真正容易被忽略的是:fragment 本身不是“高性能银弹”。如果你插入的是带复杂 CSS、动画、Web Components 的节点,瓶颈可能在样式计算或组件初始化,而非插入动作本身。










