HTML标签嵌套有严格解析规则:<p>内不能放<div>,<ul>下必须是<li>,<table>漏<tbody>会触发浏览器静默修复,导致DOM结构改变、样式失效、JS选择器失配及SSR hydration失败。

<p> 里不能放 <div>,<ul> 下必须是 <li>,<table> 里漏掉 <tbody> 就会触发浏览器静默修复——这些不是“写法偏好”,而是浏览器解析 HTML 的硬性规则。违反后不报错,但结构已变,样式和 JS 都可能失效。
为什么 <p> 里塞 <div> 会导致样式失效
浏览器遇到 <p><div>xxx</div></p> 时,会立即闭合前面的 <p>,再把 <div> 放到外面,最后补一个空 <p></p>。实际 DOM 变成:<p>xxx</p><div>xxx</div><p></p>。
后果很直接:
- CSS 中
p > div选择器完全不匹配(因为<div>已不在<p>内) - 原本靠
<p>的margin或padding控制的垂直间距消失 - JS 用
document.querySelector('p')可能拿到意外的空段落节点 - 若页面依赖服务端渲染(SSR),客户端 hydration 会失败
<ul> 和 <ol> 只能直接包 <li>
写成 <ul><div class="item">xxx</div></ul> 是非法的。浏览器会把 <div> 移出 <ul>,变成兄弟节点,<ul> 内部只剩一个空列表。
立即学习“前端免费学习笔记(深入)”;
这导致:
- 默认项目符号(
list-style)不显示 -
ul li类型选择器失效,必须改写为ul + div.item这类脆弱写法 - 语义丢失:屏幕阅读器无法识别这是列表项
- 后续加 JS 动态插入
<li>时,DOM 结构不一致
<table> 必须带 <thead>/<tbody>/<tfoot>
<table><tr><td>A</td></tr></table> 看似能运行,但 W3C 要求 <tr> 必须在 <thead>、<tbody> 或 <tfoot> 内部。浏览器会自动补 <tbody>,但补的位置不可控。
常见副作用:
- 用 CSS 选中第一行(如
tr:first-child)可能选到意外的<tbody>而非<tr> - JS 操作
table.tBodies[0].rows时,如果手写 HTML 漏了<tbody>,服务端与客户端 DOM 层级可能不一致 - 某些 CSS 框架(如 Bootstrap Table)依赖显式
<tbody>做样式隔离,漏写就导致边框/悬停失效
行内元素嵌套块级元素的典型陷阱
<a> 是特例:它被允许包裹 <div>、<h3>、<img> 等任意元素(HTML5 允许),但 <span>、<strong>、<em> 绝对不行。
例如:<span><div>xxx</div></span> 会被浏览器拆解成 <span></span><div>xxx</div><span></span>,造成布局断裂。
真正容易被忽略的是:
-
<h1>~<h6>和<dt>也只接受行内内容,放<p>或<div>同样触发自动闭合 -
<button>内不能嵌套<input>、<select>、<textarea>等可交互元素(会禁用表单提交或焦点逻辑) - 所有 void 元素(
<img>、<input>、<br>)后面加</img>不是“多写个标签”,而是创建了一个未知自定义元素,后续 DOM 会整体缩进错位



















