CSS 选择器优先级怎么计算?!important 和 @layer 怎么影响层叠?
一句话回答
多条规则作用在同一个属性上时,浏览器按层叠顺序依次比较:来源和重要性 → 行内样式 → 层(@layer)→ 选择器优先级 → 出现顺序,前一步分出胜负就不再往下比。优先级按 (ID 数, 类/属性/伪类数, 标签/伪元素数) 从左到右逐位比较,不进位。!important 会把声明提到更高的重要性级别;@layer 里后声明的层优先级更高,但对 !important 声明顺序反过来。
详细解析
层叠的比较顺序
- 来源和重要性:浏览器默认样式、用户样式、开发者样式,再叠加是否
!important。普通声明里开发者样式最高;!important声明的顺序反过来,浏览器默认样式的!important最高 - 行内样式:
style属性里的声明,赢过同一级别所有选择器写的规则 - 层:
@layer决定的先后 - 优先级:选择器的 (a, b, c)
- 出现顺序:都一样时,后写的覆盖先写的
第 1 步和第 2 步之间,规范里还有一步"封装上下文"(Shadow DOM 内外样式的比较),普通页面可以忽略。
动画和过渡在规范里是两个单独的来源:正在运行的 CSS 动画(@keyframes)的值高于所有普通声明,但低于所有 !important 声明;过渡(transition)中的值高于一切,包括 !important。
优先级 (a, b, c) 怎么算
- a:ID 选择器个数
- b:类选择器、属性选择器、伪类的个数
- c:标签选择器、伪元素的个数
- 通配符
*、组合符(空格、>、+、~)不计数
| 选择器 | 优先级 |
|---|---|
li |
(0, 0, 1) |
ul li::before |
(0, 0, 3) |
.nav a:hover |
(0, 2, 1) |
input[type="text"] |
(0, 1, 1) |
#header .logo |
(1, 1, 0) |
:is(#a, .b) p |
(1, 0, 1) |
:where(#a, .b) p |
(0, 0, 1) |
比较时从 a 开始,a 大的直接赢。所以 1 个 ID 赢过任意多个类,不存在"11 个类加起来超过 1 个 ID"。
函数式伪类
:is()、:not()、:has()本身不计数,取参数列表里优先级最高的那个:where()的优先级永远是 0,适合写容易被覆盖的基础样式(比如组件库的默认样式)
!important 和 @layer
@layer 把样式分组,层的先后由第一次声明的顺序决定:
- 普通声明:后声明的层赢,没有放进任何层的样式赢过所有层
!important声明:顺序完全反转,先声明的层赢,层里的!important赢过层外的!important
这个设计让底层的重置样式、第三方库在必要时能用 !important 守住关键规则,不被上层随手覆盖。层的作用是"先比层、再比优先级":只要层更高,选择器再简单也能赢。
继承不参与优先级
继承来的值没有优先级。只要有任何规则直接选中了元素,哪怕是 *,也会覆盖从父元素继承的值。
代码示例
/* 先声明层的顺序:reset 最低,components 最高 */
@layer reset, base, components;
@layer components {
.btn { color: white; }
}
@layer reset {
/* 层更低,即使用了 ID 选择器,普通声明也输给 components 里的 .btn */
#app button { color: black; }
}
/* 不在任何层里:普通声明赢过所有层 */
button { background: gray; }
<div style="color: red">
<!-- 结果是蓝色:p 规则直接选中了元素,继承来的红色没有优先级 -->
<p>文字</p>
</div>
<style>
p { color: blue; }
</style>
面试官可能追问
行内样式和 !important 谁的优先级高?
样式表里的 !important 赢过普通的行内样式,因为重要性比较在先。行内样式加了 !important 后,又赢过样式表里的 !important。想覆盖行内样式,要么改 JS 不再写行内样式,要么在样式表里用 !important。
为什么不推荐滥用 !important?
它破坏了正常的层叠,要覆盖一个 !important 只能再写一个更高的 !important,最后变成军备竞赛,样式很难维护。合理场景是工具类(比如 .hidden)、覆盖无法修改的第三方行内样式。有了 @layer 后,很多原来靠 !important 解决的覆盖问题,可以靠调整层的顺序解决。
第三方 UI 库的样式总是覆盖不掉,可以怎么办?
可以用 @import url(lib.css) layer(vendor); 把它整体放进一个低优先级的层,自己的业务样式放在层外或更高的层,这样不用比拼选择器就能覆盖。另一种思路是提高自己选择器的优先级,比如重复类名 .btn.btn,但不如层清晰。
易错点
- 优先级是逐位比较,不是加权求和,类再多也比不过一个 ID
- 层外的普通样式赢过所有层,但层外的
!important反而输给层里的!important :where()优先级为 0,:is()取参数中最高的,两者写法相同但结果不同
AI 模拟面试官
用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。