先记住这个答案
CSS 优先级用三列数值来比较选择器的权重。拿到一个选择器后,把里面的每个部分归入对应的列:ID 选择器归第一列,类选择器、属性选择器和伪类归第二列,元素选择器和伪元素归第三列。对 ul#nav li.active a:hover 来说,#nav 在第一列计 1,.active 和 :hover 在第二列各计 1,ul 和 li 以及 a 在第三列各计 1,最终优先级就是 (1, 2, 3)。
- ID 列只数
#id,不数[id=x] - 类、属性选择器、伪类都算第二列
- 元素选择器和伪元素算第三列
- 通配符
*和组合符不计入优先级
逐个拆解每一部分
这个选择器看着长,拆开后每个部分都是最基本的选择器类型。
/*
* 选择器: ul#nav li.active a:hover
*
* ID 列 (A): #nav → 1
* 类列 (B): .active :hover → 2
* 元素列 (C): ul li a → 3
*
* 最终优先级: (1, 2, 3)
*/
/* 优先级 (1, 2, 3) */
ul#nav li.active a:hover {
color: red;
}
/* 优先级 (0, 1, 0),输给上面的 */
.active {
color: blue;
}ul 是元素选择器,#nav 是 ID 选择器,li 是元素选择器,.active 是类选择器,a 是元素选择器,:hover 是伪类。每个部分往自己那一列加 1,拼在一起就得到 (1, 2, 3)。
组合顺序不影响结果。把选择器改写成 #nav ul li a.active:hover,优先级仍然是 (1, 2, 3),因为 ID、类、元素的数量没变。组合符 > 和 + 以及 ~ 和空格只描述元素之间的关系,本身不贡献优先级。
三列怎么比大小
比较两个选择器的优先级时从左往右看:先比 ID 列,数字大的直接赢,后面的列不用看。ID 列相等才比第二列,第二列也相等才比第三列。
这个规则意味着列和列之间不存在进位。哪怕你堆了 20 个类选择器,优先级是 (0, 20, 0),遇到一个 #id 的 (1, 0, 0) 还是输。面试的时候经常问「能不能用很多个类覆盖一个 ID」,答案就是不能——除非用 !important 把规则提升到另一个层叠层级。
三列全部相等时,CSS 会用来源顺序来决定:写在后面的规则覆盖前面的。如果项目用了 @layer,层的声明顺序优先于来源位置。
容易归错列的写法
面试常考几种看着像一类、其实归在另一列的选择器。
#nav 和 [id=nav]CSS/* #nav 是 ID 选择器,优先级 (1, 0, 0) */
#nav { color: red; }
/* [id=nav] 是属性选择器,优先级 (0, 1, 0) */
[id=nav] { color: blue; }
/* :not(.disabled) 本身不计优先级,
* 括号里的 .disabled 按类选择器计入
* 下面这条优先级是 (0, 1, 1) */
a:not(.disabled) { color: green; }[id=nav] 和 #nav 匹配的元素完全一样,但优先级差了一个量级。[id=nav] 是属性选择器,归第二列;#nav 是 ID 选择器,归第一列。面试时把这个区别说清楚,能拿到加分。
:not() 和 :is() 以及 :has() 这三个函数伪类本身不贡献优先级,优先级取决于括号里参数中最高权重的那个选择器。:where() 是特例,不管里面写什么,整体优先级都是零。
伪元素 ::before 和 ::after 以及 ::placeholder 归第三列(元素列),容易和伪类 :hover 或 :focus 搞混。区分方法很简单:两个冒号开头的是伪元素,一个冒号的是伪类。
容易答错的地方
- 以为数量多就能跨列取胜
- 三列之间没有进位关系。11 个类选择器的
(0, 11, 0)仍然输给一个#id的(1, 0, 0)。面试的时候要明确说「不能用数量弥补列级差距」。 - 忽略了
*和组合符 - 通配符
*匹配所有元素但不计入任何列。div > p的优先级是(0, 0, 2),中间的>不算。很多人把组合符也数进去,导致算出来的数字偏大。
面试官还会怎么问?
!important 和内联样式排在哪一列?
它们不在三列体系里。!important 把声明提升到一个更高的层叠层级,所有带 !important 的规则先在内部按优先级排名,再集体赢过所有普通规则。内联样式写在 style 属性里,不经过选择器,优先级天然高于任何选择器规则,只有 !important 能覆盖它。
:where() 的优先级为什么是零?
:where() 是规范设计来给「想匹配但不想提升权重」的场景用的。Tailwind CSS 的预设样式用 :where() 包裹,这样业务代码随便写一个类就能覆盖,不用加 !important。
两个选择器优先级完全相同时怎么办?
来源顺序打破平局:样式表里后出现的规则覆盖前面的。如果用了 @layer,层的声明顺序先于书写位置生效。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。