# HTML 是怎么搭出网页骨架的：从最小文档到元素、属性和嵌套

元素、标签、属性和嵌套各自解决什么问题，以及写错结构时该去哪里核对

> HTML 基础 · 语义化标签 · DOM · 约 7 分钟 · 09 月 28 日

## 本篇要点

1. 最小可用的 HTML 由 doctype、html、head、body 四层构成：doctype 让浏览器用标准模式解析，head 装不显示给用户的元信息，body 装全部可见内容。
2. 元素是开始标签、内容、结束标签合成的整体，对应 DOM 树上的一个节点；标签只是标出边界的记号。
3. 空元素如 img、br、meta、link、input 只有一个标签，不能有内容。
4. 属性写在开始标签里，描述元素本身的设置（资源地址、语言、唯一标识、给 CSS 和 JS 用的抓手），可见文字才放在内容里。
5. class 和 id 只是抓手，本身不改变外观；id 在文档内必须唯一，class 可被多个元素共用。
6. 嵌套靠“谁在谁里面”决定结构，缩进只是给人看的；规则是先开的最后关，交叉嵌套不会报错，浏览器会按容错规则猜出另一棵树。
7. p、li、td 这类元素的结束标签可以省略，因为解析器有隐含结束规则；写全结束标签更不容易出问题。
8. 检查结构要看开发者工具里的 DOM 树，而不是源文件或页面外观。
9. 标签名表达的是“这段内容是什么”而非“它长什么样”；这个语义信息供搜索引擎、屏幕阅读器、CSS 和 JavaScript 使用，div 与 span 只在找不到更合适标签时使用。

---

上一篇讲浏览器打开网页时，读到 HTML 才建出一棵 DOM 树。但那棵树具体是你用什么写出来的、为什么长成那样，上一篇只带了一句。这一篇就解决这一件事：HTML 靠什么把一个页面搭成有结构的骨架。

先记住一句总的前提：HTML 不是给人看的排版稿，而是给浏览器看的说明书。你写在文件里的字符，最终要变成内存里那棵树的节点。所以下面所有语法，都值得问一句“它在这棵树上留下了什么”。

## 最小可用的一份 HTML

```html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8" />
    <title>我的第一个页面</title>
  </head>
  <body>
    <h1>你好</h1>
    <p>这是我的第一个页面。</p>
  </body>
</html>
```

这十几个字符里，一共只有四层东西：

`<!doctype html>` 不是元素，而是一条给解析器看的声明。它必须放在文件第一行：只有写了它，浏览器才会用标准模式解析页面，其余部分才能按预期工作 [1]。

`<html>` 是根元素，页面里所有东西都在它里面。它上面带的 `lang="zh-CN"` 声明这份文档的主要语言，屏幕阅读器靠这个信息决定用哪套发音规则，翻译工具也靠它判断 [2]。

`<head>` 装的是“关于这个页面的信息”，而不是页面内容本身：编码、标题、样式表、脚本。上一篇说浏览器读 HTML 时发现 `<link>`、`<script>` 就再去发请求，那些标签通常就写在这里。注意它里面的东西用户基本看不到。

`<body>` 才装用户能看到的一切。上面那段里只有一个一级标题和一段文字。

需要留意的是：可见的文字必须写在 `<body>` 里。写在 `<html>` 下面、`<body>` 外面的文字，浏览器通常会顺手帮你塞进 `body`，但那是容错，不是你可以依赖的写法。

## 元素、标签，以及只有开始标签的那种

`<p>这是我的第一个页面。</p>` 这一整块叫一个**元素**（element）。它由三部分拼成：开始标签 `<p>`、内容 `这是我的第一个页面。`、结束标签 `</p>`。

区分“标签”和“元素”很有用：标签只是尖括号里的那个记号，作用是标出边界；元素才是整块东西，也就是上一篇里树上的一个节点。你写一对标签，浏览器就在树上多一个节点。

标签名不区分大小写，`<P>` 和 `<p>` 效果一样，但惯例都写小写 [1]。

还有一类元素根本没有内容和结束标签，比如 `<img src="cat.jpg" alt="一只猫" />`。这叫**空元素**（void element），`img`、`br`、`meta`、`link`、`input` 都属于这一类。它们的任务往往是往文档里插入或引用一个外部东西，本身不包裹任何内容，所以只有一个标签，也不能往里放东西 [3]。末尾那个 `/` 在 HTML 里可写可不写，在 XHTML 或 SVG 里才必须写。

## 属性：描述元素本身的那部分设置

属性写在开始标签里面，形式是 `名字="值"`：

```html
<img src="cat.jpg" alt="一只猫" class="photo" id="hero" />
```

属性回答的是“这个元素本身是怎么回事”，而不是“元素里放什么文字”。所以凡是给元素用的参数——资源地址、链接目标、唯一标识——都放属性；可见的文字放内容。

几个你会反复遇到的：

`src` 和 `href` 分别给出图片、脚本这类资源的地址，以及链接指向哪里。`alt` 是图片的替代文字，图片加载失败时显示，屏幕阅读器也会读它。`lang` 声明这段内容的语言，可以写在单个元素上，也可以写在根元素上让所有后代继承 [2]。

`class` 和 `id` 是留给 CSS 和 JavaScript 的抓手。`id` 在整篇文档里必须唯一；`class` 是一组用空格隔开的标记，可以被很多元素共用 [4]。重点在于它们本身不改变任何外观——`class="photo"` 不会让图片变小。外观和交互分别由 CSS、JavaScript 按这些名字去选中元素再做。

还有一类**布尔属性**，写了就是生效，不写就是不生效。比如 `disabled`，写成 `disabled` 和写成 `disabled="disabled"` 是一样的 [1]。

## 嵌套：结构真正的来源

HTML 的结构不来自缩进，而来自“谁在谁里面”：

```html
<ul>
  <li>买牛奶</li>
  <li>写代码</li>
</ul>
```

上面两行缩进纯粹是给人看的，浏览器不看缩进。真正决定结构的是 `<li>` 处在 `<ul>` 里面这件事。嵌套的正确规则只有一条：先开的标签最后关。

```html
<p>这是<strong>很重要</strong>的一句话</p>   <!-- 对：strong 在 p 里面完整结束 -->
<p>这是<strong>很重要</p></strong>           <!-- 错：两对标签交叉了 -->
```

第二行不会报错，页面可能还显示得好好的。因为 HTML 的解析器内置了容错规则，遇到交叉嵌套时它会去猜你的意图，而猜出来的结果未必是你想的那棵树。规范里有一个常被引用的例子：`<p>1<b>2<i>3</b>4</i>5</p>`。解析之后，`<i>` 被拆成了两个节点，`4` 落在新造出来的那个 `<i>` 里，而不是原来那个 [5]。

这带来一个很实际的判断方法：**不要用“页面看起来正常”来确认结构写对了**。确认的办法是打开浏览器的开发者工具，看 Elements 面板，它显示的不是你写的源文件，而是解析之后的 DOM 树。两者不一致时，以 DOM 树为准——毕竟 CSS 和 JavaScript 后面操作的也是它。

另有一条你可能已经见过的例外：有些元素的结束标签可以省略。因为解析器有“隐含结束”规则：`<p>` 遇到下一个块级元素会自己关掉；`<li>` 遇到下一个 `<li>`、表格单元格 `<td>` 遇到下一个 `<td>` 也一样 [5]。知道这条，是为了看懂别人写的（或自己漏写的）HTML 为什么结构跟想象的不一样，而不是为了省那几个字符：写全结束标签，最不容易出岔子。

## 结构不只是给浏览器“显示”用的

到这里，前面每一节的结论可以合成一句：标签名选哪个，描述的是“这段内容是什么”，而不是“它长什么样”。

`<h1>你好</h1>` 和在文字外面套个变大的样式，屏幕上可能看不出差别，但含义差得远。`h1` 表达的是“这是本页最主要的一级标题”。这个含义会被搜索引擎用来判断页面主题，被屏幕阅读器用来生成大纲、让人跳读，也被 CSS 用 `h1 { }` 直接选中 [7]。如果换成 `<div class="big-text">`，外观还在，意思丢了。

这就是所谓**语义**。`h1` 到 `h6` 是标题层级，`p` 是段落，`ul`/`ol` 配 `li` 是列表，`a` 配 `href` 是链接——这些标签让“这段东西是什么”变成机器能读的信息。`div` 和 `span` 是没有语义的通用容器，只在确实找不到更合适的标签时才用。

## 一个稍完整的样子

```html
<body>
  <h1>庞征的博客</h1>
  <p>这里记录我学前端的过程。</p>
  <ul>
    <li><a href="/posts/1">第一篇</a></li>
    <li><a href="/posts/2">第二篇</a></li>
  </ul>
</body>
```

这段对应的树（只看 body 部分）大致是：

```
body
├── h1
│   └── "庞征的博客"
├── p
│   └── "这里记录我学前端的过程。"
└── ul
    ├── li
    │   └── a  [href="/posts/1"]
    │       └── "第一篇"
    └── li
        └── a  [href="/posts/2"]
            └── "第二篇"
```

每个标签变成一个节点，嵌套变成父子关系，可见文字变成文本节点，属性挂在对应节点上。

## 这篇的边界

“哪些元素能放进哪些元素里”另有规定，叫内容模型。比如 `ul` 里只允许放 `li`，`p` 里不能放 `div` 这类块级元素 [6]。规则比“看起来合理”更严格，而且违反了同样不会报错。你以后遇到结构莫名其妙不对的情况，可以往这个方向查，但它不属于这篇的主线。

下一篇再讲怎么用这些标签把内容组织成真正的页面区块，以及让 CSS 接手的部分。现在的收获应该已经够用了：你能看懂一份 HTML 的骨架，知道元素、属性、嵌套各自负责什么，也知道结构写错时该去哪里核对。

## 术语表

- HTML 文档：一个以 .html 结尾的文本文件，里面写着一个网页的内容和结构。
- 元素（element）：开始标签、内容、结束标签合成的整体，对应 DOM 树上的一个节点。
- 标签（tag）：尖括号里的记号，用来标出元素的起止边界。
- 属性（attribute）：写在开始标签里的 `名字="值"` 形式设置，描述元素本身而不是它的内容。
- 空元素（void element）：只有开始标签、不能包含任何内容的元素，如 `img`、`br`、`meta`。
- 嵌套（nesting）：把一个元素放进另一个元素里，结构由此形成；正确嵌套要求先开的最后关。
- 解析器容错：浏览器遇到交叉或漏写的标签不报错，而是按内置规则猜出一棵 DOM 树，结果可能与你的意图不同。
- 语义（semantics）：用能说明“这是什么内容”的标签，而不是只描述外观的标签。
- 内容模型：规定某类元素内部允许出现哪些元素，违反同样不会报错。
- 全局属性：能用在任何元素上的属性，如 `class`、`id`、`lang`。

## 来源

1. [MDN：Basic HTML syntax — 元素解剖、嵌套、空元素、布尔属性与 doctype](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax)
2. [MDN：lang 全局属性 — 语言声明、继承与无障碍要求](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes/lang)
3. [MDN：Void element — 空元素的定义与结束标签规则](https://developer.mozilla.org/en-US/docs/Glossary/Void_element)
4. [MDN：Global attributes — id、class、lang 等全局属性的用途](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Global_attributes)
5. [WHATWG HTML 规范：解析 — 容错规则、交叉嵌套的树结构变化、隐含结束标签](https://html.spec.whatwg.org/multipage/parsing.html)
6. [MDN：Content categories — 内容模型，说明哪些元素能放在哪些元素里](https://developer.mozilla.org/en-US/docs/Web/HTML/Guides/Content_categories)
7. [MDN：Creating the content — head 与 body 的分工，标题、段落、列表、链接、图片的用法](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Your_first_website/Creating_the_content)

---

原文：https://pangzhengboyin.com/articles/html-elements-attributes-nesting-cbaf5438

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
