列表嵌套超3层会显著拖慢dom构建,实测低端安卓机耗时高40%,主因是解析器需为每层维护计数上下文、插入隐式并校验合法性;推荐用css counter替代深层嵌套实现编号效果。

列表嵌套超过3层会显著拖慢DOM构建
实测显示,<ol><li><ol><li><ol><li>文本</li></ol></li></ol></li></ol> 这类三层以上嵌套,在低端安卓机上 DOM 构建耗时比平铺结构高 40%。原因不是标签本身重,而是解析器必须为每层 <ol></ol> 维护独立的计数上下文、插入隐式 <li>(若遗漏)、并校验嵌套合法性——这些检查在深度增加时呈线性叠加。
- Chrome DevTools → Elements 面板右键任意
<ol></ol>→ “Show DOM properties”,查node.depth值,≥6 就已超安全阈值(注意:这里 depth 是从算起的全局深度,不是列表自身层级) - 用
document.querySelectorAll('ol ol ol')可快速定位三层嵌套列表;若返回非空,说明存在可优化点 - 避免在
<li>内直接写<div> 包裹子列表——<code><li>的合法子元素是<ol></ol>、<ul></ul>、文本等 phrasing content,<div> 会触发解析器自动拆解,导致 DOM 结构意外断裂 <h3>用 CSS counter 替代深层嵌套实现编号逻辑</h3> <p>真正需要的是“视觉编号效果”,不是“嵌套结构”。三层以上 <code><ol></ol>很可能只是为生成 2.1.1 这类编号,但代价是解析开销和语义冗余。- 平铺写法:
<ol> <li>一级</li> <li>二级</li> </ol>,再用 CSScounter-reset和counter-increment控制层级编号 - 关键样式示例:
ol { counter-reset: section; } li { counter-increment: section; } li::before { content: counters(section, ".") ". "; } - 这样既保留单层 DOM 结构(depth ≤ 4),又支持任意深度编号显示,且屏幕阅读器仍能按
<ol><li></ol>顺序朗读 - 注意:不要对
<ul></ul>使用counters(),它无序,浏览器不保证计数行为一致
多级目录场景下嵌套
<ol></ol>的唯一合理边界只有当内容天然具备**不可交换的顺序依赖**且需被辅助技术准确识别时,才应保留嵌套。例如法律条文“第十二条 → 第一款 → (一)项”,这种结构一旦扁平化,语义即丢失。
- 判断标准:把编号去掉后,读者是否无法理解条目之间的隶属关系?如果可以,就该扁平化
- 超过三层嵌套(如 3.2.1.1)几乎必然降低可读性,也超出多数 CMS 目录插件的渲染能力,建议拆分文档或用
<details><summary></summary></details>折叠子项 -
<ol></ol>内部禁止混用<ul></ul>表达“并列子项”——这会破坏顺序语义。真要混合,应外层用<ol></ol>,内层用<ol type="a"></ol>或<ol start="1"></ol>延续编号逻辑
富文本编辑器输出的列表嵌套是最大隐患源
几乎所有 WYSIWYG 编辑器(包括 TinyMCE、CKEditor、Notion 导出)默认将缩进转为深层
<ol><ol><ol></ol></ol></ol>,而非用 class 控制缩进样式。这类 HTML 在 SSR 页面中直接进入首屏 DOM,极易突破 depth=6 临界点。- 上线前必做:运行脚本
(function walk(node) { if (node.tagName === 'OL' && node.children.length && [...node.children].some(c => c.tagName === 'OL')) console.log('嵌套 OL:', node); for (let c of node.children) walk(c); })(document.body); - 服务端清洗策略:用正则或 HTML 解析器(如 jsdom)将
<ol><li><ol>...</ol></li></ol>扁平化为带 class 的单层<ol></ol>,再交由 CSS 控制缩进与编号 - 警惕
<li> <p></p> <ol>...</ol> </li>这种结构——<p></p>不是<li>的合法子元素,浏览器会自动把<p></p>提升为兄弟节点,导致 DOM 错位和选择器失效
getComputedStyle、offsetHeight等 API 调用时突然卡顿——因为此时浏览器要回溯整条祖先链匹配样式,深度每+1,路径就+1次向上查找。 - 平铺写法:











