用document.createdocumentfragment()批量插入节点可将n次重排压缩为1次,因其操作在离屏的documentfragment中进行,不触发重排;而直接循环appendchild到真实dom会因每次插入都可能触发布局计算,导致频繁重排。

用 document.createDocumentFragment() 批量插入节点,能将 N 次重排压到 1 次,前提是所有节点都先塞进 fragment,最后只调一次 appendChild 到真实 DOM。
为什么直接循环 appendChild 会卡
每次对已挂载的容器调用 appendChild,浏览器都可能触发一次重排(reflow)——尤其当容器有 flex、float、line-height 或依赖尺寸的 CSS 时。你插入 100 个 li,就可能看到 DevTools Rendering 面板里密集的 Layout 帧。
- 不是“浏览器慢”,是它被反复打断:每插一个,都要检查布局是否受影响
- 哪怕父容器
display: none,部分浏览器仍会做轻量 layout 计算 -
getBoundingClientRect()、offsetHeight这类读取操作在循环中出现一次,就会强制同步重排,彻底破坏优化
DocumentFragment 的正确组装姿势
它不是魔法开关,关键在“隔离”:所有创建和插入必须发生在 fragment 内部,且不能中途泄露到真实 DOM。
- 只调一次
document.createDocumentFragment(),别在循环里反复创建 - 用
document.createElement()或existingNode.cloneNode(true)创建节点,再frag.appendChild(node) - 不要在 fragment 上调
getComputedStyle(el)或el.offsetHeight—— 它没渲染,返回0或强制 layout - 插入后
frag自动清空,再次appendChild(frag)不会报错,但什么都不会发生
常见错误:table、innerHTML 和复用陷阱
看似写了 fragment,但几个细节会让优化失效:
- 往
table插tr时,不能直接frag.appendChild(tr)再table.appendChild(frag);必须先 append 到tbody或thead,否则浏览器静默修正结构,fragment 失效 -
frag.innerHTML = '...'是无效的 ——DocumentFragment没innerHTMLsetter,得用DOMParser或临时div中转 - 把 fragment 当缓存对象复用(比如撤销/重做逻辑里存着),插入后它已空,后续读
frag.childNodes.length得到 0,但代码可能误判为“还有内容”
替代方案对比:innerHTML vs DocumentFragment vs cloneNode
选哪个不看“快”,而看你要不要控制节点生命周期:
-
innerHTML:适合纯静态 HTML 字符串,快但需手动转义防 XSS,且每次赋值都会销毁子树、丢事件监听器和输入框值 -
DocumentFragment:适合动态创建 + 绑定事件 + 保留引用的场景(如富文本编辑器高亮行),兼容 IE9+,内存开销略高但可控 -
cloneNode(true):适合重复渲染相同结构(如列表模板),比逐个createElement快,但克隆后事件监听器不会自动继承,得手动绑定
真正起效的前提,是你确认当前卡顿确实来自频繁重排——而不是 JS 逻辑本身慢,或 CSS 触发了 will-change 这类激进更新策略。Fragment 解决的是“DOM 插入方式”问题,不是万能性能银弹。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











