CSS 选择器优先级详解,一次搞懂权重计算规则
如果你写过一段时间的 CSS,大概率遇到过这样的场景:明明写了样式,却死活不生效;改了一个类名,另一个地方的样式突然崩了;想覆盖一个第三方组件的样式,加了 !important 才勉强搞定,结果后面越改越乱。
这些问题的根源,往往都指向同一个东西------CSS 选择器优先级(Specificity) 。
这篇文章会把优先级的计算规则拆解得清清楚楚,看完之后你再也不用靠"猜"来写 CSS 了。
一、什么是选择器优先级?
当多个 CSS 规则同时匹配同一个元素,并且它们声明了同一个 CSS 属性时,浏览器需要决定:到底用谁的?
这个"决定规则"就是优先级。优先级高的胜出。
一句话记住核心原则:更具体的选择器,优先级更高。
二、优先级权重模型(四元组)
浏览器内部用一个 四位元组 (a, b, c, d) 来表示每个选择器的优先级权重。从左到右,权重逐级递减。
| 位置 | 代表来源 | 权重级别 |
|---|---|---|
| a | 内联样式(style 属性) |
最高 |
| b | ID 选择器(#header) |
高 |
| c | 类选择器、属性选择器、伪类 | 中 |
| d | 元素(标签)选择器、伪元素 | 低 |
注意:这不是十进制数字,是独立的四位计数 。即使
d的值超过 10,也不会进位到c。比如(0, 0, 0, 15)仍然小于(0, 0, 1, 0)。
三、各类选择器的权重明细
1️⃣ 内联样式 --- (1, 0, 0, 0)
css
<div style="color: red;">Hello</div>
内联样式优先级极高,仅次于 !important。
2️⃣ ID 选择器 --- (0, 1, 0, 0)
css
#header { color: blue; }
每个 ID 贡献 1 个 b 位。
3️⃣ 类选择器、属性选择器、伪类 --- (0, 0, 1, 0)
css
.nav { } /* 类选择器 */
[type="text"] { } /* 属性选择器 */
:hover { } /* 伪类 */
每出现一个,c 位 +1。多个叠加时累加:
css
.nav.active:hover /* (0, 0, 3, 0) */
4️⃣ 元素选择器、伪元素 --- (0, 0, 0, 1)
css
div { } /* 元素选择器 */
::before { } /* 伪元素 */
每出现一个,d 位 +1。
5️⃣ 通用选择器、组合符、否定伪类
css
* { } /* 通用选择器 → (0, 0, 0, 0) */
div > p { } /* > 组合符 → 不贡献权重 */
:not(.foo) { } /* :not() 本身不贡献,但 .foo 贡献 (0,0,1,0) */
:not()、:is()、:where()比较特殊,后面会单独讲。
四、复合选择器的权重计算
复合选择器(多个简单选择器组合)的权重 = 各部分权重之和。
示例逐个拆解
| 选择器 | 权重计算 | 四元组 |
|---|---|---|
div |
1 个元素 | (0, 0, 0, 1) |
.nav |
1 个类 | (0, 0, 1, 0) |
#app |
1 个 ID | (0, 0, 1, 0) → 更正:(0, 1, 0, 0) |
div.nav |
1元素 + 1类 | (0, 0, 1, 1) |
#app .nav li |
1ID + 1类 + 1元素 | (0, 1, 1, 1) |
ul.nav li.item a:hover |
2元素 + 2类 + 1伪类 | (0, 0, 3, 2) |
实战对比
css
#header .nav li a { color: red; } /* (0, 1, 2, 2) */
.nav-menu a { color: blue; } /* (0, 0, 2, 1) */
前者胜出 ✅ --- 因为有 1 个 ID 选择器。
五、特殊规则
🔴 !important --- 终极武器
css
.title { color: red !important; }
!important 不是选择器优先级的一部分,它是一个独立的覆盖机制:
diff
!important 声明 > 内联样式 > ID > 类 > 元素 > 继承 > 默认
⚠️ 强烈建议 :除非在覆盖第三方组件且别无他法时,否则不要用 !important。一旦用了,后面想覆盖它只能再加 !important,最终变成"important 军备竞赛"。
🔴 :where() --- 权重归零
css
:where(.foo, .bar) { color: red; }
:where() 内部的选择器 不贡献任何权重 ,整体权重为 (0, 0, 0, 0)。非常适合写 reset / base 样式。
对比:
css
:is(.foo, .bar) { color: red; } /* 取内部最高权重 → (0,0,1,0) */
🔴 :is() --- 取最高权重
csharp
:is(#header, .nav) p { }
/* #header 是 ID (0,1,0,0),.nav 是类 (0,0,1,0) */
/* 取最高 → ID 胜出,所以整体权重 = (0,1,0,0) + (0,0,0,1) = (0,1,0,1) */
🔴 继承的样式
继承来的样式 优先级最低 ,甚至低于通配符 *。
css
body { color: gray; }
* { color: black; } /* 这个会覆盖 body 的继承值 */
六、完整优先级排序(从低到高)
less
1. 浏览器默认样式
2. 继承的样式
3. 通用选择器 *、组合符
4. 元素选择器 div p
5. 类选择器 .nav
6. 属性选择器 [type="text"]
7. 伪类 :hover
8. 伪元素 ::before
9. ID 选择器 #header
10. 内联样式 style=""
11. !important
七、源码顺序决胜规则
当优先级完全相同时,后声明的规则胜出:
css
.btn { color: red; }
.btn { color: blue; } /* 这个生效 ✅ */
注意:是在样式表中的出现顺序,不是 HTML 中 class 的排列顺序。
八、常见误区
❌ 误区 1:class 数量多就一定赢
css
.nav.item.active.tab.link { color: red; } /* (0,0,5,0) */
#header { color: blue; } /* (0,1,0,0) */
ID 永远赢。5 个类打不过 1 个 ID。
❌ 误区 2:元素多就能覆盖类
css
body div ul li a span { color: red; } /* (0,0,0,6) */
.text { color: blue; } /* (0,0,1,0) */
1 个类 > 无数个元素选择器。
❌ 误区 3:HTML 里 class 写前面就优先
arduino
<div class="red blue">text</div>
css
.blue { color: blue; }
.red { color: red; }
生效的是 .red(后声明),跟 class 在 HTML 中的顺序无关。
九、实用建议
✅ 1. 保持选择器扁平
less
/* ❌ 过度嵌套 */
#app .sidebar .menu li a { }
/* ✅ 扁平化 */
.menu-link { }
✅ 2. 用 class 作为主要样式手段
避免依赖 ID 和元素选择器来写样式,这样优先级更可控。
✅ 3. 善用 CSS 自定义属性做覆盖
css
.card {
background: var(--card-bg, white);
}
通过修改变量来覆盖样式,完全不需要操心优先级。
✅ 4. 组件库样式覆盖的正确姿势
css
/* 用 :where 降低基础权重 */
:where(.btn) { padding: 8px 16px; }
/* 用普通类提升覆盖能力 */
.btn-primary { background: blue; }