跳到主要内容

CSS 不同选择器的权重(Specificity)与层叠规则(Cascade)

面试速答(30 秒版 TL;DR)

  • 同一元素同一属性被多条声明同时命中时,浏览器按层叠规则选“赢家”:
    • 来源与重要性(origin + !important) → @layer → 权重(Specificity) → 书写顺序(后写覆盖先写)
  • 权重不是第一关:很多“我明明写得更具体却不生效”,其实输在 !important@layer、或书写顺序上。
  • 权重用四段法记:A-B-C-D
    • A:内联样式(style=""
    • B#id
    • C.class / [attr] / :hover 等伪类
    • Ddiv 等标签 / ::before 等伪元素
  • 权重比较是逐列比较(先比 A,再比 B…),不是把数字加起来。

延伸阅读(更系统):CSS 合并方法:多条样式如何得到最终结果?CSS 权重(Specificity)CSS 继承与层叠


心智模型:层叠解决“听谁的”,权重只是其中一关

面试里最容易说错的一句话是“这条规则覆盖那条规则”。更准确的是:

  • CSS 是按元素 + 属性分别决策的:color 单独选赢家,margin-left 也单独选赢家。
  • **层叠(Cascade)**解决“多个候选值冲突时谁赢”。
  • **权重(Specificity)**只是层叠比较中的“一个阶段”,并且只在前面都打平时才用得上。

一、层叠(Cascade)常用判断顺序(面试够用版)

当你确认“选择器命中、@media/@supports 条件成立”之后,再按下面的顺序比:

  1. 来源与重要性(origin + !important
  2. 层叠层(@layer
  3. 权重(Specificity)
  4. 书写顺序(后写覆盖先写)

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 + 1style="color: red"
ID 选择器B + 1#app
类选择器C + 1.card
属性选择器C + 1[disabled][type="text"]
伪类C + 1:hover:focus:nth-child(2)
标签选择器D + 1divh1
伪元素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 相同,就比 BB 相同再比 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)
  • 权重比较:逐列比较,不做加法