CSS 选择器的特异性(specificity)会影响 CSS 的匹配,特异性越高的规则,优先级越高,最终优先级最高的规则将命中,并覆盖优先级更低的属性。
如何计算特异性
https://specifishity.com/ 这个网站用一张图清晰地阐述了CSS选择器特异性的计算,感觉看完这个就不用往下看了😅。
首先,我们按以下几类情况分别计算:
- A:ID 的个数
- B:class、attributes、pseudo-class 的个数
- C:type、pseudo-element 的个数
计算完成之后,他们的特异性可以写成 (A,B,C)
。
值得一提的是,有些 pseudo-class 会为其他选择器创建一个执行上下文,所以他们的特异性的计算方式会有一些不同:
-
:is()
、:not()
、:has()
这三个伪类的特异性会替换成他们的参数中特异性最高的那个。如
:is(em, #foo)
和#foo
是一样的。注:目前主流浏览器中只有 Firefox 78+ 原生支持
:is()
,见 MDN。 -
:nth-child()
、:nth-last-child()
这两个伪类的特异性等于自身的特异性(B=1)加上参数中特异性最高的那个。如
:nth-child(even of li, .item)
的特异性是(0,2,0)
。 -
:where()
这个伪类的特异性为0。
注:目前主流浏览器中只有 Firefox 78+ 原生支持
:where()
,见 MDN。
此外,处于存储空间的考虑,浏览器会对 A、B、C 的大小做一些限制,所以当他们的值大到一定程度后,就不会再增加了。
在某些说明中,会把内联样式(inline style)和 !important
也纳入到特异性的计算中,从而让他们也可以参与特异性的比较。这时只需要把是否有 !important
(记为D)和是否为内联样式(记为E)加到 (A,B,C)
的前面,得到 (D,E,A,B,C)
,然后按顺序比较就可以了,越往后优先级越低。
如何查看特异性
在 Safari 浏览器中,我们可以通过开发者工具查看 CSS 选择器的特异性,如下图:
如何比较优先级
以下优先级依次增加:
-
外部样式
- 计算各选择器的特异性,依次比较
(A,B,C)
:A 不同的情况下,A 较大者优先级更高;A 相同的情况下,比较 B,依次类推。
- 计算各选择器的特异性,依次比较
-
内联样式
- 优先级高于普通的外部样式
-
!important
- 当样式含有
!important
时,它的优先级高于普通的外部样式和内联样式。 - 如果两个样式都含有
!important
,则使用前面的规则决定优先级。
- 当样式含有
- 如果上面所有的优先级比较后结果都是平局,则后来者居上。
较佳实践
至此,我们就可以推断出一段样式中到底哪一条才会赢到最后。也因为上面这些规则的存在,我们可以得出一些比较好的实践方式,来更轻松、更优雅地写 CSS:
- 尽量不要用
!important
,因为它打破了原本有秩序的优先级比较,强行开了一个绿色通道,使得它引入的新的样式很难再被其他样式覆盖。 - 尽量不要用内联样式,因为不仅和
!important
类似,会让特异性的比较失效,还存在难以复用的问题。 - 对于通用的样式,尽量使优先级更低,以便在需要小范围定制的时候,我们可以轻松地通过简单的约束,来产生优先级更高、代码依然优雅的样式。
这里再补充一个例子吧,ant-design 作为一个著名的 UI 框架,其样式代码就是一个典型的不想让开发者覆盖的例子。它的 CSS 代码几乎是按照 DOM 结构来嵌套的,这使得它的每个样式的特异性都非常高,如果我们要对它进行覆盖,几乎总是需要写一个比它更长的选择器。
这里并不是说它的代码写得不好,毕竟它的定位是一个设计方案而不是一个单纯的 UI 库。但是从普通开发者的角度来看,如果你想让你的 CSS 可维护性更高,更方便被定制,那就要尽量避免高优先级、高特异性的选择器,如果代码未来还是要自己来维护的,就给未来的自己留条路。