documentfragment能显著减少重排次数,因其是离线轻量容器,操作时不触发渲染,仅在一次性插入真实dom时执行一次布局和绘制。

直接批量插入比逐个 appendChild 快 5–10 倍,关键不在“快”,而在避免浏览器反复重排——这是绝大多数人忽略的底层代价。
用 DocumentFragment 替代多次 appendChild
每次调用 appendChild 到真实 DOM,浏览器都可能触发一次重排(reflow),尤其当父容器已挂载、有样式依赖时。而 DocumentFragment 是离线节点,操作它不触发任何渲染流程。
- 不要这样写:
container.appendChild(div)在循环里执行 100 次 - 应该先创建
const fragment = document.createDocumentFragment(),所有div都fragment.appendChild(div) - 最后只调用一次
container.appendChild(fragment) - 注意:
fragment插入后自动清空,不可复用;如需多批,每次新建
避免 innerHTML += 字符串拼接
看似简洁,实则灾难:el.innerHTML += htmlString 会强制浏览器先序列化现有 DOM → 拼接字符串 → 全量解析重建整个子树,性能随节点数平方级恶化。
- 哪怕只加一行
<tr>,也会销毁并重建全部已有 <code><tr> <li>替代方案:仍走 <code>DocumentFragment路线,或用insertAdjacentHTML('beforeend', htmlString)(仅追加,不重建) - 若必须拼 HTML 字符串,请确保是完整结构(如整张表),而非片段;且提前做
escapeHtml()防 XSS - 合并写操作:用
el.style.cssText = 'color:red;font-size:14px;'一次性设置 - 更推荐:用
el.className = 'active-item'切换预定义 CSS 类,语义清晰、性能更高 - 若需动态计算值(如根据高度设 margin),务必先批量读取(
els.map(el => el.offsetHeight)),再批量写入,绝不交叉 -
setTimeout(..., 0)只保证“尽快”,不保证在帧结束前执行,可能挤占渲染时间 -
requestIdleCallback显式告诉浏览器:“我在空闲时干活”,天然适配渲染帧节奏 - 示例节流逻辑:
if (deadline.timeRemaining() > 1) { processBatch(); requestIdleCallback(loop, { timeout: 1000 }); } - 注意兼容性:IE 不支持,需 fallback 到
setTimeout,但别用0,至少设16毫秒
批量更新已有 DOM 节点时慎用 style 属性赋值
对已有元素循环改样式,比如 els[i].style.color = 'red'; els[i].style.fontSize = '14px';,每行都可能触发样式计算,叠加读取布局属性(如 offsetHeight)就会引发布局抖动(Layout Thrashing)。
大数据量场景下,requestIdleCallback 比 setTimeout(..., 0) 更可靠
渲染 10,000 行表格时,即使用了 DocumentFragment,一次性插入仍会让主线程卡顿。需要分片,但调度时机很关键。
真正卡顿的从来不是“DOM 多”,而是“DOM 操作方式让浏览器无法喘气”。批量只是手段,核心是尊重浏览器渲染流水线——读写分离、离线组装、主动让权。这些细节在小项目里不显眼,一旦数据量上万、交互频繁,就是页面是否跟手的分水岭。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











