本节摘要:元素是 HTML 文档的构件,标签是元素的书写形式。本节拆解元素的四个组成部分(开始标签、属性、内容、结束标签),讲清 void 元素、可省略标签、内容模型与嵌套规则,回答"p 里为什么不能放 div"这一经典问题。
不从定义开始,从一段实物开始。看这个元素(注意:示例里的链接地址用了井号锚点,仅作演示):
<a href="#references" class="link-primary">参考资料</a>
按标准的说法,这一整行是一个元素(element)。它由四部分组成:开始标签、元素的内容"参考资料"、结束标签,以及写在开始标签里的两个属性。日常口语里大家把"写一个标签"和"写一个元素"混着用,多数时候无伤大雅。但当你读到规范条文"该元素的内容模型为短语内容"时,主语是元素不是标签——标签只是语法糖纸,元素才是放进文档树里的那个节点。
浏览器解析这段代码后,内存里会生成一个锚元素节点,它带着两个属性键值对和一个文本子节点。整份文档解析完,所有元素组成一棵树,也就是后面章节会反复提到的 DOM(文档对象模型)。语法模型是纸面规则,DOM 是运行结果,两者一一对应但不是一回事。
阅读完本节,你应当能够:
有一类元素天生没有内容,也就没有结束标签,标准称之为 void 元素。完整清单不长:area、base、br、col、embed、hr、img、input、link、meta、param、source、track、wbr。它们的共同点是"所有信息都在属性里":img 的图片地址在 src,meta 的编码声明在 charset,input 的类型在 type。
写法上有两种历史习惯:HTML5 风格写 void 元素时结尾不带斜杠,XHTML 习惯在末尾带一个空格加斜杠。今天的解析器对两种写法都接受,斜杠会被直接忽略。团队里统一成哪种都行,但别对非 void 元素使用这种写法——自闭合形式的 div 在 HTML 解析器眼里只是个没闭合的开始标签,后面的内容全都会被当成它的子节点,这是从 JSX 回来写原生 HTML 的人最常见的翻车点。
<!-- 在 HTML 解析器眼里,下面两行的 div 都没有闭合 --> <div class="a" /> <p>这段文字会变成上面 div 的子节点</p> <!-- 正确写法:老老实实给结束标签 --> <div class="a"></div> <p>现在两个元素是兄弟关系</p>
把这两段分别存成文件用浏览器打开,再按 F12 看元素面板,你会发现第一份文档的 p 被塞进了 div 里面——解析器没有报错,它只是按自己的规则"理解"了你的代码。
HTML 的嵌套不是自由的。标准为每个元素规定了内容模型(content model),声明它允许包含哪一类内容。几个关键类别:
类别的包含关系是分层的:流内容涵盖短语内容,但反过来不成立。p 的内容模型是短语内容,而 div 属于流内容但不属于短语内容,所以"p 里放 div"不合法。这不是审美建议,是语法层面的规定。浏览器遇到这种写法会在解析阶段自动补救——在 div 之前先闭合 p,于是 DOM 结构变成三个兄弟节点:p、div、又一个 p。你写的源码和浏览器手里的树已经不是同一棵了。
一个更隐蔽的同类问题:a 元素里嵌套 a。规范不允许交互内容嵌套交互内容,解析器遇到内层 a 会先闭合外层。类似的还有 form 嵌 form。这些规则的共同出发点是交互语义的二义性:点一下内层,算谁的?
<!-- 你写的 --> <p>段落开头<div>一个块</div>段落结尾</p> <!-- 浏览器实际构建的 DOM --> <p>段落开头</p> <div>一个块</div> 段落结尾 <!-- 裸文本"段落结尾"沦为 div 的后续兄弟,不再是段落 -->
标准允许某些标签在特定上下文中省略。最有名的是 li 的结束标签:列表项后紧跟另一个 li 或列表结束时,</li> 可以不写。同理还有 p(后跟块级元素时)、tr、td、th、dt、dd 等。
<ul> <li>第一项 <li>第二项 </ul>
解析器能正确还原。但我在工程上的态度是:能不省就不省。省略依赖对上下文的精确判断,重构时代码一挪动,原来合法的省略就可能变成事故;而且源码与 DOM 的一致性对排查问题是刚需。宽容语法是浏览器给用户的(页面不轻易白屏),不是给开发者偷懒的。省几个字符,换一个"源码树 ≠ 实际树"的暗雷,不划算。
标签与属性的书写规则再列三条硬性的:元素名与属性名合法字符是拉丁字母数字,HTML 里大小写不敏感但惯例全小写;属性值含空格、引号或非字母数字字符时必须加引号,惯例是统统加引号;属性之间用空白分隔,属性值内部的空格属于值本身。布尔属性(如 disabled、checked)可以只写名不写值,等价于值为空字符串,第 1.3 节展开。
标准没有规定最大嵌套层数,但浏览器实现有上限(通常上百层),日常页面远够不着。真正的问题在可读性:超过六七层的缩进通常意味着结构设计有问题,比如用 div 一层层包裹只为凑布局——那是 CSS 的职责被错放进了 HTML。后面第 3 章讲表现与结构分离时回头看这一点,会有更深的体会。
缩进风格没有标准答案,两空格四空格都能活,关键是全项目一致。缩进在 HTML 里只是源码层面的排版,解析器把标签之间的连续空白折叠成一个空格,所以缩进不影响渲染结果——除了 pre 元素内部,那里空白一字不改地保留。这个"空白折叠"规则也解释了为什么源码里敲十个空格只显示一个,以及为什么行内元素之间的换行会在页面上留下一个空格的缝隙(第 3 章布局部分会再遇到这个缝隙)。
源码是线性的,DOM 是树形的,这个转换值得亲眼看一次。把下面这段文档跑起来,再打开浏览器开发者工具的元素面板,你会看到与示意图一致的结构:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>树形结构演示</title> </head> <body> <h1>产品手册</h1> <p>本手册分为<em>安装</em>与<em>配置</em>两部分。</p> <ul> <li>第一章 安装</li> <li>第二章 配置</li> </ul> </body> </html>

注意示意图里的细节:文本也是节点(title 里的标题文字、p 里被 em 切开的文字段),脚本注释不是节点(被解析器丢弃),而 void 元素天然没有子节点。树上每个节点都是后续 CSS 选择器与脚本操作的最小单位——第 3 章讲选择器时,你会发现"选择器选的就是这棵树上的位置"。
把上文提到的类别整理成速查表,第 2 章讲语义化时还会扩充它:
| 类别 | 能出现在哪里 | 代表元素 |
|---|---|---|
| 元数据内容 | head 内 | title、meta、link、style |
| 流内容 | body 内的大多数位置 | div、p、h1-h6、table、form |
| 短语内容 | 流内容内部,文本级 | a、em、strong、span、img、input |
| 标题内容 | 流环境 | h1 到 h6 |
| 章节内容 | 流环境 | section、article、nav、aside |
| 交互内容 | 流环境 | a带链接、button、input、select |
| 嵌入内容 | 流环境 | img、video、canvas、iframe |
一个判断嵌套合法性的快速心法:查外层元素的内容模型允许哪些类别,再查内层元素属于哪些类别,取交集。p 要求短语内容,div 只属于流内容(不属于短语内容),交集为空,所以不合法;span 既属于短语内容又属于流内容,放哪都行——这也解释了为什么 span 是"万能行内容器"而 div 是"万能块容器":它们的差异不在显示,而在类别归属派生出的嵌套许可。
💡 关键直觉:不是"块级不能放行内",而是"内容模型的类别交集决定一切";块级、行内是 CSS 的显示概念,与嵌套合法性没有直接关系,很多人把这两套概念混了多年。
HTML 解析器里有一块"特区",处理方式与普通元素不同:当它遇到 svg 或 math 根元素时,会切换到外来内容(foreign content)解析模式。这个模式下语法规则更接近 XML——大小写敏感、属性写法更严格。所以你会在内联 SVG 里看到 camelCase 的属性名(比如 viewBox),而 HTML 普通区域里属性名大小写无所谓。这也解释了一个常见困惑:把 SVG 里的 viewBox 写成 viewbox,浏览器不认;把普通 HTML 元素的 CLASS 写成大写却照样生效。记住分界线:进入 svg 或 math 内部,就按 XML 的规矩来。

顺带一提,script 与 style 内部也是特殊区域:解析器把这两个元素的内容当纯文本(script)或纯字符数据(style)处理,直到遇见对应的结束标签才恢复 HTML 模式。这就是为什么 script 代码里出现小于号不需要转义——那段字节流根本没被当作 HTML 解析。第 5 章讲脚本加载策略时,这个机制还会再出场一次。
练习一:打开本节任意示例,把一个 h2 的结束标签删掉,再在下一段后面补上,刷新后去元素面板找 h2 的边界到哪里为止。你会对"结束标签缺失时的隐式闭合规则"有体感——标题元素一直延伸到下一个块级元素开始。
练习二:写一个 ul,故意在两个 li 之间塞一个 p,观察解析器把列表拆成了什么样。再试着把 p 换成 span,对比合法与非法嵌套在 DOM 上的差别。
练习三:写一段内联 SVG,把 viewBox 故意改成全小写,观察图形显示尺寸的变化。这个实验能帮你记住特区规则,也为第 2 章多媒体与图形一节的 canvas、svg 对比做好铺垫。
下一节聚焦开始标签里那些"名值对"——属性,它是元素的配置面,也是 CSS 和 JavaScript 找到元素的坐标系。