CSS 选择器优先级怎么计算?
简化版
CSS 优先级按来源、重要性、选择器权重、书写顺序共同决定。选择器权重通常按内联样式、ID、类/属性/伪类、元素/伪元素比较,权重相同后写的覆盖先写的。
详细版
常见权重顺序:
!important会提高声明优先级,但仍受来源和层叠规则影响。- 内联样式通常高于普通选择器。
- ID 选择器高于类选择器。
- 类、属性、伪类同级。
- 元素、伪元素较低。
- 通配符、组合符本身不增加权重。
例如:
#app .btn { color: red; }
.page .btn.primary { color: blue; }
第一条有一个 ID,通常会赢。实际项目中不建议滥用 ID 和 !important,否则样式很难覆盖。更好的方式是控制选择器复杂度,配合组件作用域、CSS Layers 或 BEM 命名。
完整版教学
一、CSS 层叠不是只看“谁更长”
很多人误以为选择器越长优先级越高,但真正比较的是权重类别。#id 的权重比很多类叠加还高,因为 ID 在更高的权重位。
可以粗略记成四元组:
内联样式, ID 数量, 类/属性/伪类数量, 元素/伪元素数量
比较时从左到右看,前一位大的直接胜出。
二、书写顺序什么时候生效
只有当前面的层叠来源、重要性、选择器权重都相同时,后出现的声明才覆盖先出现的声明。
.btn { color: red; }
.btn { color: blue; }
这里后者生效,因为权重完全一样。
但下面不一样:
#submit { color: red; }
.btn.primary { color: blue; }
即使第二条写在后面,ID 权重仍然更高。
三、:where 和 :is 的面试加分点
:where() 的优先级永远是 0,适合写基础样式而不增加覆盖成本。
:where(article h2) {
margin-top: 2rem;
}
:is() 的优先级取决于参数里权重最高的选择器。现代 CSS 面试中提到这两个点,能体现你不仅会老规则,也关注新特性。
四、工程里的控制策略
大型项目里要避免选择器越写越重。比较健康的策略是:
- 少用 ID 写样式。
- 少用
!important,只在工具类或覆盖第三方样式时谨慎使用。 - 组件样式保持浅层选择器。
- 全局样式用低权重策略,组件样式负责具体表现。
这样样式系统才不会进入“为了覆盖而不断加权重”的泥潭。
五、面试追问与工程落地
优先级题进一步会问 !important 是不是最高。更准确的说法是:它会改变声明的重要性层级,但仍要放在完整层叠规则里比较,包括来源、cascade layer、选择器、顺序等。用户样式里的 important、浏览器默认样式、作者样式之间还有来源优先级差异。
现代 CSS 还引入了 @layer。层级靠后的 layer 会覆盖靠前 layer 中同等来源的样式,即使选择器权重较低也可能胜出。这让大型项目可以把 reset、基础样式、组件样式、工具类分层,减少靠加选择器权重解决覆盖问题。
工程里真正要避免的是“权重军备竞赛”。如果你发现需要 .page .panel .content .btn.primary 才能覆盖样式,通常说明样式边界设计有问题。更好的方案是降低全局选择器权重、组件化作用域、使用 CSS Modules 或 CSS Layers。
六、现场计算一次选择器权重
规范比较的是 ID、类类项、类型类项三个分量,并按字典序逐位比较,不存在“十个类进位成一个 ID”。内联样式属于层叠流程里的独立维度,用四元组帮助记忆可以,但不要把它误说成选择器语法本身的权重分量。
| 选择器 | 权重 (ID, 类/属性/伪类, 类型/伪元素) | 说明 |
|---|---|---|
#app .btn | (1, 1, 0) | 一个 ID、一个类 |
.page .btn.primary | (0, 3, 0) | 三个类仍输给前者的 ID 位 |
button::before | (0, 0, 2) | 一个类型、一个伪元素 |
:where(#app) .btn | (0, 1, 0) | :where() 及其参数贡献为零 |
:is(.btn, #submit) | (1, 0, 0) | 取参数列表中最高权重 |
若两个普通作者声明分别匹配 (1, 1, 0) 与 (0, 20, 0),比较第一位就结束,前者获胜。只有来源、重要性、上下文、层叠层和权重等更前条件都相同时,才轮到作用域接近度与书写顺序。
面试中把
!important直接写成“权重无穷大”不够准确;它改变的是声明的重要性层级,之后仍会在相应层级中继续执行完整层叠比较。
七、常见误区与追问
- 误区:选择器写得越长,优先级就一定越高。 长度不是比较单位,关键是 ID、类类项和类型类项各自出现多少以及它们所在的层级。
- 误区:十个类选择器可以抵消一个 ID 选择器。 各分量按字典序比较且不进位,只要 ID 分量更大,后续分量再多也不会翻转结果。
- 误区:
:is()、:not()和:has()自身都固定贡献一个伪类权重。 它们的权重由参数选择器列表中最高的权重替代,而:where()始终贡献零。 - 追问:
!important是否能覆盖内联样式? 同一来源下的 important 声明会优先于普通内联声明,但不同来源、层叠层和动画过渡仍要按完整层叠顺序判断。 - 追问:
@layer为什么能减少权重军备竞赛? 普通作者样式先按层顺序比较,后声明的层可在无需提高选择器权重的情况下覆盖前层。 - 追问:同权重声明一定是后写的生效吗? 还要先保证来源、重要性、层、作用域等条件相同;这些都相同后,后出现的声明才胜出。
- 追问:为什么基础样式适合用
:where()? 它允许写有上下文约束的匹配条件却保持零权重,使组件样式更容易按正常层叠覆盖。
八、加强记忆
CSS 优先级的核心是“先分层,再比权重,最后看顺序”。不要用选择器长度判断胜负;真正要看 ID、类、元素分别有多少,以及是否有内联和 !important。