documentfragment 是将多次重排合并为一次的离线操作机制,解决浏览器反复 layout 而非 js 执行慢;需每次新建、仅插入一次真实 dom,插入后自动清空不可复用。

DocumentFragment 不是“更快的 appendChild”,而是把 N 次重排压成 1 次的离线操作机制——它解决的不是 JS 执行慢,而是浏览器被反复逼着 layout。
为什么循环调用 appendChild 会卡主线程
每次对已挂载容器调用 appendChild,浏览器都可能立即触发样式计算 + 布局测量,尤其当父元素有 float、position: absolute、flex 或 grid 等依赖布局的样式时。
- 100 次插入 ≠ 100 次 JS 执行,而是最多 100 次潜在 layout 计算
- 哪怕容器是
display: none,旧版 Safari 或读取offsetHeight这类触发器仍会强制同步重排 - Performance 面板里能看到大量 Layout 任务堆积,滚动掉帧、input 输入延迟、动画跳变都是外显症状
正确创建和使用 document.createDocumentFragment()
它不是渲染节点,而是一个不参与渲染管线的离线容器。关键在“只插一次真实 DOM”。
- 必须用
const frag = document.createDocumentFragment()每次新建,不能复用已插入过的 fragment(插入后自动清空) - 所有
createElement出来的节点,先frag.appendChild(node)或frag.append(node1, node2) - 最后仅调用一次
container.appendChild(frag),此时 fragment 子节点被“迁移”过去,fragment 自身变为空 - 不要对 fragment 调用
querySelector或设innerHTML——它不支持,也不该支持
什么时候必须选 DocumentFragment 而非 innerHTML
字符串拼接快,但代价是失控:事件监听器丢失、表单 value 重置、dataset 清零、XSS 风险暴露。
- 你已有现成节点(比如从
<template></template>克隆的li、带data-id的按钮),用innerHTML就得先outerHTML再 parse,纯浪费 - 需要为每个节点单独绑定不同事件或设置动态
class,字符串无法表达逻辑分支 - 内容来自用户输入或富文本编辑器输出,
textContent+DocumentFragment天然防 XSS,不用手写escapeHtml() - 目标环境需兼容 IE9+(
append()方法 IE10+ 才支持,但appendChild+DocumentFragment全版本通吃)
在线运行环境(如 CodePen、JSFiddle)中特别容易踩的坑
这类沙箱常把代码注入 iframe 或 sandboxed div,DOM 更新更敏感,一次长循环 + 多次 appendChild 很容易卡住预览区。
- 误用
el.innerHTML += htmlString:每次都会清空旧内容、全量解析重建子树,对 100 行列表就是 100 次解析 + 100 次回流 - 用户输入未转义就拼进
innerHTML,是 XSS 高危入口 - 沙箱里常禁用
eval和内联事件,innerHTML覆盖后直接丢失已有事件监听器或dataset - 若需解析 HTML 字符串(如 Markdown 渲染结果),应走临时
div中转:const temp = document.createElement('div'); temp.innerHTML = htmlString; while (temp.firstChild) frag.appendChild(temp.firstChild);
真正容易被忽略的点在于:fragment 插入后自动清空,不可复用;很多人写完一次就缓存起来反复用,结果后续插入什么都没发生——因为 fragment 已空。它不是状态容器,只是批处理的一次性中转站。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











