CSS 不同选择器的权重(Specificity)与层叠规则(Cascade)
面试速答(30 秒版 TL;DR)
- 当同一元素的同一属性被多条声明同时命中时,浏览器按层叠规则选“赢家”:
- 来源与重要性(origin +
!important) →@layer→ 权重(Specificity) → 书写顺序(后写覆盖先写)
- 来源与重要性(origin +
- 权重不是第一关:很多“我明明写得更具体却不生效”,其实输在
!important、@layer、或书写顺序上。 - 权重用四段法记:
A-B-C-D:A:内联样式(style="")B:#idC:.class/[attr]/:hover等伪类D:div等标签 /::before等伪元素
- 权重比较是逐列比较(先比 A,再比 B…),不是把数字加起来。
延伸阅读(更系统):CSS 合并方法:多条样式如何得到最终结果?、CSS 权重(Specificity)、CSS 继承与层叠。
心智模型:层叠解决“听谁的”,权重只是其中一关
面试里最容易说错的一句话是“这条规则覆盖那条规则”。更准确的是:
- CSS 是按元素 + 属性分别决策的:
color单独选赢家,margin-left也单独选赢家。 - **层叠(Cascade)**解决“多个候选值冲突时谁赢”。
- **权重(Specificity)**只是层叠比较中的“一个阶段”,并且只在前面都打平时才用得上。
一、层叠(Cascade)常用判断顺序(面试够用版)
当你确认“选择器命中、@media/@supports 条件成立”之后,再按下面的顺序比:
- 来源与重要性(origin +
!important) - 层叠层(
@layer) - 权重(Specificity)
- 书写顺序(后写覆盖先写)
1.1 !important 的关键点(别背错)
!important会把声明放到“更高的重要性赛道”里优先比较。- 它不是万能钥匙:如果双方都是
!important,仍然要继续比@layer、权重、书写顺序。 - 外部样式的
!important可以覆盖内联普通声明:
<h1 id="t" class="title" style="color: blue">Hello</h1>
/* ✅ 会赢:important 赛道优先于内联的普通声明 */
.title {
color: red !important;
}
注:真实规范里还有 user / user-agent 样式来源的差异。面试/工程里一般把“作者样式(author)”当主战场就够了。
二、权重(Specificity)怎么算:A-B-C-D 四段法
2.1 计分表(高频必背)
| 选择器片段 | 计入哪一段 | 示例 |
|---|---|---|
| 内联样式 | A + 1 | style="color: red" |
| ID 选择器 | B + 1 | #app |
| 类选择器 | C + 1 | .card |
| 属性选择器 | C + 1 | [disabled]、[type="text"] |
| 伪类 | C + 1 | :hover、:focus、:nth-child(2) |
| 标签选择器 | D + 1 | div、h1 |
| 伪元素 | D + 1 | ::before、::marker |
2.2 不加分的(最常踩坑)
| 片段 | 是否计入 | 说明 |
|---|---|---|
通配符 * | ❌ | 不加权重 |
| 组合符 | ❌ | 空格、>、+、~ 都不加权重 |
:where(...) | ❌ | :where() 以及其参数选择器都不计入权重 |
2.3 比较规则:逐列比较,不做加法
权重可以记成四个数字:A-B-C-D。
0-1-0-0(1 个 ID)一定大于0-0-99-99(再多 class/tag 也赢不了 ID)- 当
A相同,就比B;B相同再比C;以此类推
三、典型题(面试最常出)与标准答法
Q1:.a .b 和 .a > .b 谁权重大?
答:权重一样,都是 0-0-2-0。因为组合符(空格 / >)不加分。
.a .b {
color: blue;
}
.a > .b {
color: red;
}
如果两条都命中同一元素同一属性,通常会走到“书写顺序”:后写的赢。
Q2:为什么选择器写得更长,还是打不过另一个?
答:长度不重要,看“计分段位”。典型就是 ID 的段位更高:
/* 0-1-0-0 */
#app {
color: red;
}
/* 0-0-3-3:再长也赢不了 ID */
.page .content .title h1 strong {
color: blue;
}
Q3:class="a b" 里 class 的顺序会影响谁生效吗?
答:不会。class 的先后顺序不参与层叠比较。若两条规则同一来源/层且权重相同,通常就是谁的 CSS 写在后面谁赢。
Q4::is() / :not() / :where() 的权重陷阱(加分题)
:where(...):权重永远是 0(即使里面写了#id也不加分):is(...)/:not(...)/:has(...):权重取其参数列表里最“重”的那个选择器
/* 仅 #app 记分;:where(.title) 不加分 -> 0-1-0-0 */
#app :where(.title) {
color: red;
}
/* #app + .title -> 0-1-1-0,比上面更高 */
#app .title {
color: blue;
}
/* :is(#app, .container) 的权重取 #app -> 0-1-0-0;再加 .title -> 0-1-1-0 */
:is(#app, .container) .title {
color: green;
}
四、常见追问:工程上怎么少打“权重大战”
- 尽量用类选择器做组件化命名(BEM/约定式命名),少用 ID 参与样式竞争。
- 用
@layer做分层(如reset/base/components/utilities):先在“层级”上规定覆盖方向,再在层内谈权重。 - 限制选择器深度(少写“祖先链很长”的选择器),避免局部样式变成全局难以覆盖。
!important只作为最后手段(或工具类体系的明确策略),否则很容易把可维护性打穿。
易错点/坑
- 讨论权重之前,先确认:选择器是否命中?
@media/@supports是否成立?是不是被别的规则在更高赛道(!important/@layer)拦截了? - 组合符不加权重:
.a .b不会比.a>.b更“重”。 :where()不加权重:它常被用来“写得更长但不提高权重”,方便后续覆盖。
速记要点(可背)
- 层叠顺序(常用版):来源与重要性 →
@layer→ 权重 → 后写覆盖先写。 - 权重四段:内联(A) > ID(B) > class/属性/伪类(C) > 标签/伪元素(D)。
- 权重比较:逐列比较,不做加法。