document.createdocumentfragment主要减少重排(reflow),因它不属主文档树,操作时不触发布局计算,仅最后插入时统一更新;重绘(repaint)随之减少。

直接用 document.createDocumentFragment 不能减少“重绘”(repaint),它主要减少的是“重排”(reflow);而重绘是否减少,取决于重排是否被触发——没重排,自然也少连带重绘。很多编辑器场景里,用户误以为卡顿是重绘导致,实际是频繁插入引发的布局抖动。
为什么 document.createDocumentFragment 对编辑器 DOM 操作特别有用
HTML 编辑器常需动态生成大量行、高亮块、光标占位符或实时预览节点。比如 Markdown 实时渲染:每次输入触发重新解析并插入 20+ 个 <div> 或 <code><span></span>。若逐个 appendChild 到编辑容器,每插一个都可能让浏览器检查行高、换行、字体度量——尤其当容器有 white-space: pre-wrap、line-height 或 font-feature-settings 时,重排开销陡增。
document.createDocumentFragment 把这些节点先“离线攒好”,最后只调一次 container.appendChild(frag),浏览器只需做一次布局计算和一次绘制提交。
- 它不挂载到文档树,所以
frag.appendChild(el)不会触发任何样式计算 - 编辑器中常见“插入后立刻读取
el.offsetHeight”的逻辑,这会强制同步重排——必须挪到frag插入真实 DOM 之后再读 - 如果编辑器支持撤销/重做,别把
frag当缓存对象复用:它插入后自动清空,再次appendChild(frag)什么都不会发生
在富文本编辑器中批量插入高亮行的正确写法
假设你要把解析后的语法高亮结果(每个 <span></span> 带不同 class)插入到 contenteditable 容器:
const frag = document.createDocumentFragment();
const tokens = parseAndHighlight(text); // 返回 [{tag: 'span', cls: 'keyword', text: 'function'}, ...]
tokens.forEach(token => {
const el = document.createElement(token.tag);
el.className = token.cls;
el.textContent = token.text;
frag.appendChild(el); // ✅ 离线操作,零开销
});
// ⚠️ 注意:不要在这里调用 getComputedStyle(el) 或 el.offsetTop
editorContainer.appendChild(frag); // ✅ 只有这一句触发重排
常见错误是边建节点边测尺寸:frag.appendChild(el); console.log(el.offsetHeight);——el 还没挂到真实 DOM,offsetHeight 返回 0,但某些浏览器会隐式触发 layout 计算,破坏优化效果。
容易被忽略的边界情况
编辑器场景下这几个点最容易翻车:
-
DocumentFragment不支持querySelector或getElementById:你不能对frag调用frag.querySelector('.cursor')来找光标节点——它还没进文档,CSS 选择器不生效 - 不能用
frag.innerHTML = '<span>xxx</span>':DocumentFragment没有innerHTMLsetter,会静默失败 - 如果编辑器依赖
Range和Selection(比如光标定位),插入前必须确保 fragment 已挂载;临时方案是先appendChild到隐藏的<div style="position: absolute; visibility: hidden"> 测量,再移走 <li>使用 <code>cloneNode(true)复制已有高亮节点时,事件监听器不会被复制,需手动重新绑定——而innerHTML方案根本没法绑定事件
真正影响编辑器流畅度的,往往不是 fragment 用没用,而是“读布局”和“写 DOM”有没有混在同一个循环里。哪怕用了 fragment,只要在插入前反复读取 getBoundingClientRect() 或 offsetTop,优化就基本白搭。











