CSS 选择器特异性(Specificity)详解

在 CSS 样式系统中,当多条规则同时作用于同一个 DOM 元素且声明了相同的属性时,浏览器必须依据一套确定的算法来决定最终应用哪条规则。这套算法的核心即为特异性(Specificity)。理解并掌握特异性计算机制,是编写可预测、可维护 CSS 代码的前提。任何对样式的“意外覆盖”或“失效”的排查,本质上都是对特异性权重的重新审视。

一、 特异性的计算模型

CSS 规范将选择器的特异性定义为一个由四个分量组成的有序元组,通常记作 (A, B, C, D)。比较时遵循从左至右、逐位比较的原则,高位权重永远大于低位权重之和。不存在“10个类选择器等于1个ID选择器”的进位逻辑。

各分量的定义如下:

  • A(Inline Style): 内联样式。仅当样式通过 HTML 元素的 style 属性直接声明时为 1,否则为 0。注意:<style> 标签内的样式不属于此列。
  • B(ID Selectors): ID 选择器的数量。统计选择器中所有 #id 形式的个数。
  • C(Class, Attribute, Pseudo-class): 类选择器(.class)、属性选择器([attr])以及伪类选择器(:hover, :nth-child(), :not() 等)的数量总和。
  • D(Type, Pseudo-element): 类型选择器(元素标签名)和伪元素选择器(::before, ::after, ::placeholder 等)的数量总和。

关键修正: 在早期的 CSS2 教学中,特异性常被简化为三位数(如 1-2-3)。但在现代 CSS(Selectors Level 4)规范中,必须明确区分内联样式与选择器本身,因此采用四元组模型更为严谨。此外,通配符 *、组合器 +, >, ~, 以及否定伪类 :not() 本身的括号不贡献特异性值,但 :not() 内部的参数选择器需正常参与计算。

二、 特殊关键字与优先级干预

在标准特异性计算之外,存在两个具有绝对优先级的关键字,它们凌驾于常规计算模型之上:

  1. !important 被标记为 !important 的声明拥有最高优先级。其比较规则独立于普通声明:

    • !important 声明始终覆盖非 !important 声明。
    • 当两条 !important 声明冲突时,特异性较低者反而胜出(这是为了允许用户样式表覆盖作者样式表中的 !important)。
    • 若特异性也相同,则后出现的声明生效。

    工程警示: 在生产环境中应严格限制 !important 的使用。它会破坏正常的层叠秩序,导致后续维护成本呈指数级增长。仅在工具类(Utility Classes)或覆盖第三方不可控样式时酌情使用。

  2. @layer 与 Cascade Layers CSS 层叠层(Cascade Layers)引入了比特异性更上层的分组机制。位于显式 @layer 中的样式,其优先级低于未分层(unlayered)的样式。在各层内部,仍按常规特异性计算;但跨层比较时,层级顺序决定一切。这为大型项目的样式架构提供了新的治理手段。

三、 常见认知误区澄清

  • 误区一:“特异性是十进制数值” 特异性不是数学意义上的数字。(0,1,0,0) 永远大于 (0,0,15,0),因为 B 位 > C 位,无论 C 位的数值多大。
  • 误区二:“DOM 结构越深,特异性越高” 特异性仅与选择器字符串本身有关,与匹配到的 DOM 节点深度、嵌套层级无关。div p span 的特异性是 (0,0,0,3),并不会因为实际 DOM 嵌套了十层而增加。
  • 误区三:“:not() 本身增加特异性” :not(.active) 的特异性等同于 .active,即 (0,0,1,0):not() 伪类自身不计分,仅其参数计分。但 :not(#id.class) 会累加内部所有参数的特异性。
  • 误区四:“后写的样式一定生效” 仅在特异性完全相同时,源码顺序(Source Order)才作为决胜条件。特异性不同时,顺序无关紧要。

四、 特异性速查对照表

选择器示例 A (Inline) B (ID) C (Class/Attr/Pseudo-class) D (Type/Pseudo-element) 特异性元组 备注
style="..." 1 0 0 0 (1,0,0,0) 内联样式,最高常规优先级
#header #nav 0 2 0 0 (0,2,0,0) 双 ID,B 位主导
#sidebar .widget a:hover 0 1 2 1 (0,1,2,1) ID + 类 + 伪类 + 类型
.nav .item.active 0 0 3 0 (0,0,3,0) 三个类选择器叠加
[type="text"]:focus 0 0 2 0 (0,0,2,0) 属性选择器 + 伪类
ul li:nth-child(2n+1) 0 0 1 2 (0,0,1,2) 伪类计 C 位,标签计 D 位
p::first-line 0 0 0 2 (0,0,0,2) 类型 + 伪元素均计 D 位
:not(.disabled) 0 0 1 0 (0,0,1,0) :not() 自身不计分,参数计分
* > div + p 0 0 0 2 (0,0,0,2) 通配符与组合器不计分
div p span a 0 0 0 4 (0,0,0,4) 纯类型选择器链

五、 工程实践建议

  1. 保持选择器扁平化: 避免超过三层的选择器嵌套。过高的特异性会使组件样式难以被外部合理覆盖,违背组件化设计原则。
  2. 以类选择器为主要钩子: BEM、SMACSS 等方法论的核心价值之一,就是将特异性稳定控制在 (0,0,x,0) 区间内,使权重可预测、可管理。
  3. 善用 :where() 降低特异性: CSS Selectors Level 4 引入的 :where() 伪类,其内部参数特异性恒为 0。在构建可复用组件库时,可用 :where() 包裹默认样式选择器,为用户预留无摩擦的覆盖空间。
  4. 利用开发者工具验证: 现代浏览器 DevTools 的 Styles 面板可直接显示每条规则的特异性值及被覆盖原因。遇到样式冲突时,应以工具验证为准,而非凭经验猜测。

特异性是 CSS 层叠机制的基石,而非需要规避的缺陷。精确理解其运作原理,方能从“试错式调样式”进阶为“确定性样式工程”。