# CSS 是怎么落到元素上的：样式引入、选择器与层叠优先级

样式写在哪里、选择器如何命中元素，以及多条规则冲突时由来源、特异性和出现顺序依次裁决

> CSS 基础 · 选择器与层叠 · 约 7 分钟 · 09 月 28 日

## 本篇要点

1. 一条 CSS 规则由选择器和声明块组成，声明块里每条声明只管一个属性；冲突是逐条声明、按属性分开比的，一条声明输了不影响同一条规则里其他属性。
2. 样式写在外部 `<link>`、内部 `<style>`、行内 `style` 属性里，最终都会变成声明；外部和内部在层叠里地位相同，行内样式因为没有选择器，权重高于任何用选择器写的正常声明。
3. 选择器的作用是去 DOM 树上匹配元素，没命中的规则完全不参与比较；所以冲突只在“恰好都命中同一个元素”的规则之间发生。
4. 层叠按固定顺序判定：先看来源与重要性，再看特异性，最后看出现顺序。
5. 正常声明里作者样式优先于浏览器默认样式，所以 `p { margin: 0; }` 就能改掉 `<h1>`、`<p>` 的默认外观，不需要 `!important`；来源这一层排在特异性之前。
6. 特异性写成 ID、CLASS、TYPE 三列计数，从左往右比较，不相加；一个 class 就能赢过任意多个元素名叠加。
7. 特异性完全相同时，写在后面的规则生效，包括 `<head>` 里多个样式表或 `<style>` 的先后顺序。
8. 行内样式和 `!important` 是压过上述规则的两种例外，代价是难以再被覆盖，所以不该当常规手段。
9. 继承与层叠不是一回事：继承值只在没有任何声明命中该属性时才起作用，一条低特异性的直接声明也能压过它。
10. 调试时用浏览器 Styles 面板看被划掉的声明，按来源、特异性、顺序三步定位，而不是靠猜。

---

上一篇你把 HTML 写成了一棵 DOM 树。那棵树只说清了“页面上有什么、谁包含谁”，一个字也没说它们长什么样。CSS 就是给这棵树上每个节点设样式的一套规则。

新手写 CSS 时最常卡住的三类问题，正好对应三件事：

- 样式写在哪里？写在 `<link>` 里、`<style>` 里还是标签的 `style` 属性里，有区别吗？
- 一条规则会选中哪些元素？
- 两条规则都选中同一个元素、又都想设同一个属性，最后听谁的？

这三件事串起来就是“CSS 怎么落到元素上”。下面按这个顺序讲。

## 一条 CSS 规则长什么样

```css
p {
  color: gray;
  font-size: 16px;
}
```

`p` 是**选择器**（selector），负责圈出“这条规则管哪些元素”。花括号里是**声明块**，里面每一条 `属性: 值;` 叫一条**声明**（declaration），一条声明只管一个属性。

这里有个容易被忽略但后面很关键的性质：规则不是一个整体。将来发生冲突时，浏览器是逐条声明、按属性分开比的。一条规则里的 `color` 输了，同一条规则里的 `font-size` 照样能生效。

## 样式可以放在三个地方

**外部样式表**，放在 `<head>` 里：

```html
<link rel="stylesheet" href="style.css" />
```

写在一个独立文件里，多个页面共用，改一处全站生效。这是实际项目里的常规做法。

**内部样式表**，直接把 CSS 写在页面的 `<style>` 里：

```html
<style>
  p { color: gray; }
</style>
```

只有这个页面能用，适合练手和单页小效果。

**行内样式**，写在开始标签的 `style` 属性里：

```html
<p style="color: gray;">这是一段话。</p>
```

只能作用在这一个元素上，没法复用。

三者的共同点是：最终都会变成“某些元素的某些属性上的一条声明”。真正的差别有两点。

第一，行内样式很特殊。它没有选择器，只有值，浏览器把它当成专属于这一个元素的声明，权重比任何用选择器写出来的正常声明都高。所以它能盖住你的样式表里写的一切——这正是它不该作为常规手段的原因：一旦用了，你只能靠更极端的办法才能改回来。

第二，外部和内部样式表在层叠里地位相同。所以 `<head>` 里 `<link>` 和 `<style>` 谁写在前面、谁写在后面，是会影响结果的，原因在“出现顺序”那一节。

## 选择器：先圈出一组元素

浏览器对每一条规则做的事是：拿它的选择器去 DOM 树上匹配，匹配上的元素才拿到声明块里的声明 [2]。看一个例子：

```html
<ul class="menu">
  <li><a href="/" class="active">首页</a></li>
  <li><a href="/about">关于</a></li>
</ul>
```

```css
a { color: gray; }
.menu a { color: navy; }
.active { font-weight: bold; }
```

- `a` 是类型选择器，页面上所有 `<a>` 都命中，两个链接都在内。
- `.menu a` 分两步读：先找 `class` 属性里带 `menu` 的元素，再在它里面找 `<a>`。只有“首页”那个链接命中。
- `.active` 找 `class` 里带 `active` 的元素，也命中“首页”。

这几种写法值得先记住：`.x` 按 `class` 选，`#x` 按 `id` 选（`id` 整页唯一），`:hover` 按状态选（鼠标悬停时才命中）。选择器里的空格表示“在后代里找”。它们全都是筛选条件，本身不设置任何样式——上一篇说 `class` 和 `id` 是留给 CSS 的抓手，这里就是落地的样子。

要建立的心智模型是：**先匹配，再比权重**。一条规则对这个元素要么命中、要么没命中；没命中的规则完全不参与后面的比较。所以你不用去比整个样式表，只比“恰好都命中了这个元素”的那几条。

## 一个元素的一个属性只有一个值

浏览器渲染时，DOM 树上每个元素的每个属性最终只能取一个值。冲突只发生在“同一个属性的多条声明”之间。

反过来说，不同属性互不干扰。所以“我的样式被覆盖了”这个说法通常不准确：不是整条规则被扔掉了，而是其中某几条声明被压下去了，其余的还在生效。

## 谁说了算：层叠的三步

CSS 全称 Cascading Style Sheets 里的 Cascading，指的就是这套排序算法。它分步骤进行，前面的步骤分出胜负就不再往下比 [2]：

1. 相关性：选择器命中这个元素了吗？
2. 来源与重要性：这条声明来自谁，带没带 `!important`？
3. 特异性：同一个来源内部，选择器“有多具体”。
4. 出现顺序：前面都没分出高下时，写在后面的赢。

**来源**。对初学者来说，值得关心的是两类：浏览器自带的默认样式（叫 user-agent 样式表），和你写的作者样式。正常声明里，作者样式优先级更高 [2]。

这一条直接解释了一个你天天见到、可能没想过为什么的现象：`<h1>` 默认很大、`<p>` 默认有上下边距，你只要写一行 `p { margin: 0; }` 就能改掉。你的规则和浏览器默认规则不在同一个来源上，作者样式直接胜出——不需要 `!important`，也不需要很“具体”的选择器。这也说明来源这一层排在特异性前面：来源不同时，去比选择器谁更具体是没意义的。

**特异性**用来比较同一个来源里的几条规则。它的写法是三列计数：

- ID 列：选择器里用了几个 `#id`。
- CLASS 列：用了几个 `class`、属性选择器、伪类（`:hover` 算在这里）。
- TYPE 列：用了几个元素名。

比较时**从左往右**：ID 列大的直接赢；ID 列相同才比 CLASS 列；再相同才比 TYPE 列。不是把三列加总分。

回到菜单那个例子。`.menu a` 是“1 个 class + 1 个元素名”，记作 `0-1-1`；`a` 是 `0-0-1`。链接的 `color` 两条规则都想设，ID 列都是 0，CLASS 列一个是 1、一个是 0，所以 `.menu a` 赢——“首页”是深蓝色，与 `a` 那条规则写在哪一行无关。

再看一个能纠正直觉的例子：`body p { color: gray; }` 是 `0-0-2`，`.active { color: navy; }` 是 `0-1-0`。类选择器只要出现一个，就赢过任意多个元素名叠加，因为元素名只进第三列，而比较从左往右进行。顺带一个细节：选择器里的空格、`>` 这类组合符只描述位置关系，不计入任何一列。

**出现顺序**负责收尾。特异性完全相同时，后面写的赢：

```css
.alert { color: orange; }
.alert { color: green; }
```

绿色生效，因为它在后面。

最后是两个例外。行内样式的属性没有选择器，权重可以看成比任何选择器都高（常写作 `1-0-0-0`），所以它能盖过上面所有正常声明 [1]。`!important` 写在声明的末尾，会在同一个来源内部压过所有不带它的声明，不管特异性多高 [3]。它危险的地方在于：一旦你用了它，普通声明就再也压不回来，只能再写一个 `!important`，越写越乱。把它留给“改别人写的、你控制不了的样式”这类少数场合；日常遇到“某个颜色没生效”，先按来源、特异性、顺序这三步查。

## 没有任何规则命中时

有时候你压根没给某个元素写 `color`，它的颜色也变了。原因是某些属性会从父元素**继承**下来：你在 `body` 上写 `color: #333`，整页文字都跟着变深。

但要区分清楚：继承和层叠是两码事。继承来的值只在**没有任何声明命中这个属性**时才会被用上；只要有一条声明直接命中这个元素，哪怕它只有 `0-0-1` 这么低的特异性，也是它赢，继承值根本不参与比较 [4]。

所以“给 `body` 设了字体，个别元素却不一样”通常不是继承失效，而是那些元素被某条规则直接命中了。

## 样式不生效时怎么查

上一篇让你用 Elements 面板确认 DOM 树长什么样。这一篇对应的是 Styles 面板：选中一个元素，它会把所有命中这个元素的规则按层叠结果列出来，被压下去的声明会画一道删除线，同时标出它来自哪个文件、哪一行。浏览器默认样式通常列在最上方（标着 user agent stylesheet）。

看到删除线，就说明这条声明输了，接着按上面的顺序问：来源不同吗？特异性谁大？谁写在后面？多数时候三句话之内就能找到原因，不用靠猜或者乱加 `!important`。

到这里，“CSS 怎么落到元素上”这条链就闭环了：写在哪个位置只是起点，真正决定外观的是“谁命中，以及命中之后层叠怎么排”。

## 术语表

- 选择器：一条 CSS 规则开头用来圈定“这条规则管哪些元素”的部分，浏览器拿它去 DOM 树上做匹配。
- 声明与声明块：`属性: 值;` 是一条声明，只管一个属性；花括号里的若干条声明合起来是声明块，冲突时按声明逐条比较。
- 特异性：同一个来源内部比较选择器“有多具体”的三列计数，依次是 ID、CLASS、TYPE，从左往右比，不相加。
- 层叠：CSS 用来决定同一个属性上多条冲突声明谁生效的排序流程，顺序为来源与重要性、特异性、出现顺序。
- 来源：一条声明出自哪里；正常声明里作者样式（你写的 CSS）优先于浏览器自带的默认样式。
- 出现顺序：来源和特异性都相同时，写在后面的声明生效。
- 行内样式：写在标签 `style` 属性里的声明，没有选择器，权重高于用选择器写的正常声明。
- `!important`：写在声明末尾的标记，让它在同一来源内压过所有不带该标记的声明，代价是很难再被覆盖。
- 继承：属性值从父元素传给子元素；只在没有任何声明命中该属性时才起作用，与层叠是两套机制。

## 来源

1. [MDN: Specificity — 特异性的三列计算方式、从左往右比较的规则，以及行内样式的权重说明](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Specificity)
2. [MDN: Introduction to the CSS cascade — 层叠算法的步骤顺序、来源优先级，以及来源先于特异性比较的说明](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade/Introduction)
3. [MDN: Handling conflicts — 面向初学者的冲突处理讲解，含 !important 覆盖规则与源顺序的示例](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/Handling_conflicts)
4. [MDN: CSS cascading and inheritance — 层叠与继承模块总览，说明继承与层叠分别解决什么问题](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascade)

---

原文：https://pangzhengboyin.com/articles/how-css-applies-to-elements-05ba549f

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