CSS 选择器优先级怎么算?!important 一定能覆盖其他样式吗?
下面是一段教学用的模拟面试。
🧑💻 面试官:两个 CSS 规则都能选中按钮,最后听谁的?
🙋♂️ 我:看选择器权重。ID 比类高,类比标签高,一样就用后面的。
🧑💻 面试官:那低权重规则加了 !important 呢?另一个规则在不同的 layer 里呢?
🙋♂️ 我:要先看层叠规则,不能直接算选择器。
🧑💻 面试官:所以 !important 就永远最大吗?:where(#app) 的权重又该怎么算?
权重比较只是其中一关。先看声明有没有资格竞争,再比较选择器有多具体。
面试速答(60 秒版)
CSS 为同一元素的同一属性选值时,先按层叠规则处理来源、重要性、层叠层等因素;到了相应竞争范围,才比较选择器优先级。
普通选择器可以按 ID、类这一组、类型这一组组成三列,按从左到右比较。类这一组包括类、属性选择器和伪类;类型这一组包括元素类型与伪元素。不是把它们当十进制数字相加。
!important 会改变声明的层叠位置,不是给选择器增加权重,也不保证覆盖一切。:where 本身及参数的优先级为零;:is、:not、:has 则采用参数选择器列表中最高的优先级。排查覆盖时,先看浏览器实际命中的声明和所属层,再判断谁胜出。

知识点详解:按钮明明写了蓝色,为什么还是红色?
先确定竞争的是同一属性
假设页面按钮同时匹配 .button 和 #save,两条规则都设置 color。
它们是在竞争这个按钮的文字颜色。另一条规则设置 background,不会因为选择器更具体,就把 color 也一并接管。
还有一个常见误会:父元素的 ID 规则把文字设成红色,子按钮自己用简单类型选择器设成蓝色。这里父元素的颜色通过继承传下来,不是把父选择器权重拿来和子按钮直接匹配的规则决斗。
因此,排查前先看:属性是什么,声明直接作用于谁,是否来自继承。CSS 层叠说明给出了完整的筛选顺序。

三列比较,不是“类写够十个就顶一个 ID”
对于普通选择器,咱们可以写出三列计数:
| 选择器 | ID | 类、属性、伪类 | 类型、伪元素 |
|---|---|---|---|
| #save | 1 | 0 | 0 |
| .toolbar .button:hover | 0 | 3 | 0 |
| button.button | 0 | 1 | 1 |
比较从最左列开始。#save 的 ID 列是 1,另外两条是 0,在这个可比较的范围里它就胜出,不必再把右边几列加起来。
所以“ID 100 分,类 10 分,标签 1 分”只能是很粗糙的记忆说法。真实规则不是十进制进位,十个类不会自动兑换成一个 ID。
通用选择器 * 和组合符本身不增加这三列。内联样式也不宜硬塞进这个普通选择器三列表;在普通作者样式的层叠中,它另有位置。
为什么要先看 layer,再算权重?
假设咱们把第三方按钮样式放进 vendor 层,自己的覆盖规则放进后声明的 app 层:
@layer vendor, app;
@layer vendor {
#save { color: red; }
}
@layer app {
.button { color: blue; }
}
在这两个普通作者声明中,后声明的 app 层优先,所以按钮可以是蓝色,即使 .button 的选择器优先级低于 #save。
这不是选择器计算失灵,而是两条声明先在层叠层这一关分出了结果,还没有轮到跨层比较选择器。MDN 优先级说明明确提醒,选择器只在相应来源与层的范围内比较。
同一来源的普通声明中,未放进层的样式还高于命名层里的普通样式。但重要声明的层顺序会反转,不能拿普通声明的顺序口诀直接套在 !important 上。

!important 为什么不能叫“永远最高”?
在同一来源、同一层内,一条声明加 !important,通常会压过普通声明,即使普通声明有更具体的选择器。
但如果两条都是 important,还要继续判断来源、层、内联位置等因素,到了同一竞争范围才比较选择器。
例如用户重要样式可以优先于作者重要样式;正在生效的 CSS transition 也有更高的层叠位置。因此,!important 不是一个无条件覆盖所有来源和状态的开关。
工程里遇到覆盖问题,不能不断补 ID、不断加 important。先把第三方样式和自己的层次安排清楚,往往比让选择器越写越长更容易维护。
:where 和 :is,看起来相似,权重却不同
:where(#app) .button { color: blue; } /* 0-1-0 */
:is(#app, .panel) .button { color: red; } /* 1-1-0 */
:where 的参数里虽然有 ID,但这一部分优先级是零,只剩 .button 的一项类计数。
:is 则使用参数列表中最高的选择器优先级。#app 比 .panel 高,于是它贡献 ID 那一列,再加外面的 .button。:not、:has 的参数也有相应规则。
这说明优先级不能只靠肉眼数文本里出现了几个井号。需要知道它在哪一种选择器结构里。
DevTools 里应该怎样查?
选中按钮,找到目标属性,先确认规则是否真的匹配,再看被划掉的声明和最终值。
检查它来自哪个样式表、哪一层、是否 important 或内联,之后才计算选择器。若前面都相同,再看作用域接近程度等规则和声明顺序;不能把“后写的一定赢”当作第一条规则。
本题讨论的是属性声明的层叠,不是 z-index 的层叠上下文。按钮显示在另一个元素上面,并不意味着它的 color 规则也更高。
面试官继续追问
内联样式一定覆盖样式表吗?
普通作者内联样式高于普通作者样式表声明,但重要声明与其他来源仍然需要按层叠规则判断。不能省略“普通作者声明”这个条件。
多写几个类,能超过 ID 吗?
在选择器优先级的三列比较里,不能靠右列进位超过左列。但层或 important 等前置规则可能让低优先级选择器所在声明胜出,这是另一回事。
选择器优先级相同,就一定是后面的赢吗?
还要看其他层叠因素是否相同,例如作用域接近程度。普通、同层、同等条件下,声明顺序才成为最后的取舍依据。
面试速记卡
- 先后:先判断层叠竞争范围,再比较选择器优先级。
- 计算:ID、类这一组、类型这一组,三列从左到右比较,不进位。
- !important:改变层叠位置,不增加选择器权重,也不是绝对最高。
- 函数选择器::where 为零;:is、:not、:has 采用参数中最高优先级。
- 排查:同一元素同一属性,查看命中声明、来源、层与最终值。
公司面试真题
这道题暂未收录可核验的公司真题来源。你可以先阅读本文解析,或浏览已收录的公司面试真题。
浏览公司面试真题 →