1.2 元素与标签的语法模型


1.2 元素与标签的语法模型

本节摘要:元素是 HTML 文档的构件,标签是元素的书写形式。本节拆解元素的四个组成部分(开始标签、属性、内容、结束标签),讲清 void 元素、可省略标签、内容模型与嵌套规则,回答"p 里为什么不能放 div"这一经典问题。

直接解剖一段代码

不从定义开始,从一段实物开始。看这个元素(注意:示例里的链接地址用了井号锚点,仅作演示):

<a href="#references" class="link-primary">参考资料</a>

按标准的说法,这一整行是一个元素(element)。它由四部分组成:开始标签、元素的内容"参考资料"、结束标签,以及写在开始标签里的两个属性。日常口语里大家把"写一个标签"和"写一个元素"混着用,多数时候无伤大雅。但当你读到规范条文"该元素的内容模型为短语内容"时,主语是元素不是标签——标签只是语法糖纸,元素才是放进文档树里的那个节点。

浏览器解析这段代码后,内存里会生成一个锚元素节点,它带着两个属性键值对和一个文本子节点。整份文档解析完,所有元素组成一棵树,也就是后面章节会反复提到的 DOM(文档对象模型)。语法模型是纸面规则,DOM 是运行结果,两者一一对应但不是一回事。

学习目标

阅读完本节,你应当能够:

  1. 精确区分"元素"与"标签"两个术语;
  2. 说出 void 元素清单及其写法规范;
  3. 理解内容模型如何决定嵌套合法性;
  4. 解释浏览器解析器对非法嵌套的自动纠错行为;
  5. 掌握标签与属性的合法大小写、引号、空格规则。

void 元素与自闭合写法

有一类元素天生没有内容,也就没有结束标签,标准称之为 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),声明它允许包含哪一类内容。几个关键类别:

  • 元数据内容(metadata content):head 里那些描述文档本身的元素,如 title、meta、link、style。
  • 流内容(flow content):body 里允许出现的大多数元素,p、div、h1、table 都属此类。
  • 短语内容(phrasing content):文本级的零件,如 a、em、strong、span、img、input。
  • 标题内容章节内容:h1 到 h6;section、article、nav、aside(第 2 章展开)。

类别的包含关系是分层的:流内容涵盖短语内容,但反过来不成立。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 树长什么样

源码是线性的,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>

文档到 DOM 树的映射示意

文档到 DOM 树的映射示意

注意示意图里的细节:文本也是节点(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 的显示概念,与嵌套合法性没有直接关系,很多人把这两套概念混了多年。

外来内容:SVG 与 MathML 的特区

HTML 解析器里有一块"特区",处理方式与普通元素不同:当它遇到 svg 或 math 根元素时,会切换到外来内容(foreign content)解析模式。这个模式下语法规则更接近 XML——大小写敏感、属性写法更严格。所以你会在内联 SVG 里看到 camelCase 的属性名(比如 viewBox),而 HTML 普通区域里属性名大小写无所谓。这也解释了一个常见困惑:把 SVG 里的 viewBox 写成 viewbox,浏览器不认;把普通 HTML 元素的 CLASS 写成大写却照样生效。记住分界线:进入 svg 或 math 内部,就按 XML 的规矩来。

外来内容:SVG 与 MathML 的特区

顺带一提,script 与 style 内部也是特殊区域:解析器把这两个元素的内容当纯文本(script)或纯字符数据(style)处理,直到遇见对应的结束标签才恢复 HTML 模式。这就是为什么 script 代码里出现小于号不需要转义——那段字节流根本没被当作 HTML 解析。第 5 章讲脚本加载策略时,这个机制还会再出场一次。

本节要点回顾

  • 元素是构件,标签是写法:开始标签 + 属性 + 内容 + 结束标签构成一个元素,进入 DOM 成为一个节点。
  • void 元素无内容无结束标签:img、br、input、meta 等十几个,信息全在属性里;斜杠写法可选。
  • 内容模型决定嵌套合法性:p 只装短语内容,div 不属于短语内容,所以 p 装不下 div。
  • 解析器会自动纠错:非法嵌套被静默修补,源码树与实际 DOM 可能不一致,排查布局问题要看元素面板而非源码。
  • 可省略标签是规范许可,不是工程建议:保持源码与 DOM 一致更值钱。
  • 空白折叠:标签间连续空白合并为一个空格,唯 pre 内部例外。
  • svg 与 math 是解析特区:内部按 XML 规矩,属性大小写敏感;script 与 style 内部按纯文本处理。

边玩边验证的三道练习

练习一:打开本节任意示例,把一个 h2 的结束标签删掉,再在下一段后面补上,刷新后去元素面板找 h2 的边界到哪里为止。你会对"结束标签缺失时的隐式闭合规则"有体感——标题元素一直延伸到下一个块级元素开始。

练习二:写一个 ul,故意在两个 li 之间塞一个 p,观察解析器把列表拆成了什么样。再试着把 p 换成 span,对比合法与非法嵌套在 DOM 上的差别。

练习三:写一段内联 SVG,把 viewBox 故意改成全小写,观察图形显示尺寸的变化。这个实验能帮你记住特区规则,也为第 2 章多媒体与图形一节的 canvas、svg 对比做好铺垫。

下一节聚焦开始标签里那些"名值对"——属性,它是元素的配置面,也是 CSS 和 JavaScript 找到元素的坐标系。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U