直接 innerhtml += 会卡死浏览器,因其每次执行都需全量序列化、拼接、重建整个 dom 子树,导致重排、丢失状态且主线程冻结;应改用 insertadjacenthtml 或 documentfragment 批量插入。

为什么直接 innerHTML += '' 会让页面卡死
浏览器解析 innerHTML 是全量重建行为:每次赋值都会先序列化当前子树 → 拼接字符串 → 重新解析整个 HTML 片段。哪怕只加一行 <tr>,已有 5000 行也会被销毁再重建一次。这不仅触发多次重排(reflow),还会丢失焦点、滚动位置和 input 值。
<p>实测中,对一个含 1 万行的 <code>tbody 执行 100 次 innerHTML += '<tr><td>x</td></tr>',耗时可达 4.2 秒以上,且主线程完全冻结。
- 永远不要在循环里用
innerHTML += 追加表格行
- 替代方案只有两个:
DocumentFragment 批量插入,或 insertAdjacentHTML('beforeend', html)
- 若必须拼字符串,请确保是完整结构(如整张表),而非片段;并提前做
escapeHtml() 防 XSS
table-layout: fixed 不是可选项,是强制前提
默认 table-layout: auto 下,浏览器必须扫描每一行每个 <td> 的内容宽度来统一分配列宽——10 万行 × 8 列,意味着至少 80 万次文本测量,直接拖垮主线程。开启 <code>table-layout: fixed 后,浏览器只读第一行或 <col> 定义,后续所有行跳过内容测量。
- 必须在
<table> 元素上显式声明 <code>style="table-layout: fixed",CSS 类或 reset.css 可能被覆盖
- 配合
<col width="120"> 设置列宽,比 th { width: 120px } 更可靠
-
word-break: break-word 要加在 <td> 上,否则长文本会撑破固定列宽
<li>禁用 <code>min-width、flex 或 max-content 等会破坏 fixed 计算逻辑的 CSS 值
DOM 复用的本质是“池化 + dataset 映射”
虚拟滚动不是靠删 DOM 来省性能,而是复用固定数量的 <tr> 节点,仅更新其内容与 <code>dataset.index。例如维持 30 行节点池,滚动时根据 scrollTop / rowHeight 算出起始数据下标,再批量 textContent = data[i].col1。
- 避免用
v-if 或 innerHTML 重建节点,直接复用已有 tr 并改 textContent 或 dataset
- 每行必须设
data-index,用于快速定位真实数据,避免遍历查找
- 行高必须固定或预计算(如缓存每行高度数组),否则无法准确映射可视区域
- 慎用
querySelectorAll('td') 遍历更新——改用 row.cells[0].textContent = ... 直接访问更高效
requestIdleCallback 不是银弹,要配节流与 fallback
requestIdleCallback 告诉浏览器“我在空闲时干活”,但它不保证执行时机。在低端设备或高负载下,可能长时间得不到回调,导致数据迟迟不渲染。更重要的是,它在 IE 中完全不可用。
- 必须提供 fallback:
setTimeout(fn, 16)(非 0),确保至少按帧节奏执行
- 每次回调中检查
deadline.timeRemaining() > 1,只处理能完成的小批量(如 200 行)
- 不要在回调里做 layout 读取(如
offsetHeight),否则引发布局抖动
- 真正卡顿的从来不是“DOM 多”,而是“DOM 操作方式让浏览器反复重排”——这点最容易被忽略
浏览器解析 innerHTML 是全量重建行为:每次赋值都会先序列化当前子树 → 拼接字符串 → 重新解析整个 HTML 片段。哪怕只加一行 <tr>,已有 5000 行也会被销毁再重建一次。这不仅触发多次重排(reflow),还会丢失焦点、滚动位置和 input 值。
<p>实测中,对一个含 1 万行的 <code>tbody 执行 100 次 innerHTML += '<tr><td>x</td></tr>',耗时可达 4.2 秒以上,且主线程完全冻结。
- 永远不要在循环里用
innerHTML +=追加表格行 - 替代方案只有两个:
DocumentFragment批量插入,或insertAdjacentHTML('beforeend', html) - 若必须拼字符串,请确保是完整结构(如整张表),而非片段;并提前做
escapeHtml()防 XSS
table-layout: fixed 不是可选项,是强制前提
默认 table-layout: auto 下,浏览器必须扫描每一行每个 <td> 的内容宽度来统一分配列宽——10 万行 × 8 列,意味着至少 80 万次文本测量,直接拖垮主线程。开启 <code>table-layout: fixed 后,浏览器只读第一行或 <col> 定义,后续所有行跳过内容测量。
- 必须在
<table> 元素上显式声明 <code>style="table-layout: fixed",CSS 类或 reset.css 可能被覆盖 - 配合
<col width="120">设置列宽,比th { width: 120px }更可靠 -
word-break: break-word要加在<td> 上,否则长文本会撑破固定列宽 <li>禁用 <code>min-width、flex或max-content等会破坏 fixed 计算逻辑的 CSS 值 - 避免用
v-if或innerHTML重建节点,直接复用已有tr并改textContent或dataset - 每行必须设
data-index,用于快速定位真实数据,避免遍历查找 - 行高必须固定或预计算(如缓存每行高度数组),否则无法准确映射可视区域
- 慎用
querySelectorAll('td')遍历更新——改用row.cells[0].textContent = ...直接访问更高效 - 必须提供 fallback:
setTimeout(fn, 16)(非 0),确保至少按帧节奏执行 - 每次回调中检查
deadline.timeRemaining() > 1,只处理能完成的小批量(如 200 行) - 不要在回调里做 layout 读取(如
offsetHeight),否则引发布局抖动 - 真正卡顿的从来不是“DOM 多”,而是“DOM 操作方式让浏览器反复重排”——这点最容易被忽略
DOM 复用的本质是“池化 + dataset 映射”
虚拟滚动不是靠删 DOM 来省性能,而是复用固定数量的 <tr> 节点,仅更新其内容与 <code>dataset.index。例如维持 30 行节点池,滚动时根据 scrollTop / rowHeight 算出起始数据下标,再批量 textContent = data[i].col1。
requestIdleCallback 不是银弹,要配节流与 fallback
requestIdleCallback 告诉浏览器“我在空闲时干活”,但它不保证执行时机。在低端设备或高负载下,可能长时间得不到回调,导致数据迟迟不渲染。更重要的是,它在 IE 中完全不可用。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











