specificity 是按 (a,b,c,d) 四元组逐位比较的规则:a=内联样式(0或1),b=ID数,c=类/属性/伪类总数,d=元素/伪元素总数;比较时从左到右,不进位;:not()内内容计分,:is()取内部最高,:where()归零;!important 不改数值,但提升层叠优先级。

specificity不是简单加法,也不是“谁写在后面谁赢”,它是一套严格按位比较的四元组规则。
权重怎么算:不是相加,是分位计数
每个选择器拆解后,分别统计四类成分的数量,填入 (a,b,c,d) 四元组:
-
a:仅当存在style=""内联样式时为1,否则为0 -
b:ID 选择器个数(如#header、#app) -
c:类名(.btn)、属性选择器([type="submit"])、伪类(:hover、:nth-child(2))三者之和 -
d:元素名(div、p)和伪元素(::before、::after)之和
例如:#nav .menu li.active:hover → 1 个 ID(b=1) + 2 个类/伪类(.menu、:hover,注意 .active 是类,:hover 是伪类,共 c=3) + 2 个元素(li、a?等等——这里没 a,只有 li,所以 d=1)→ 实际是 #nav .menu li.active:hover 含 li 和隐含的被选元素(假设是 a),但按字面只有 li 一个元素;若完整写成 #nav .menu li.active:hover a,则 d=2。关键:只数选择器里**明确写出的**元素/伪元素,不脑补。
比较时严格从左到右,不进位也不换算
两个 specificity 值(比如 (0,1,1,1) 和 (0,0,11,0))不是比“110”和“110”,而是逐位比:
- 先比
a:都是0→ 继续 - 再比
b:1 > 0 → 前者胜出,后面c、d完全不看
这就是为什么 #id(0,1,0,0)永远压得过 11 个类(0,0,11,0)——b 位的 1 比 c 位的任何数都重。别信“100 vs 110”这种十进制类比,它会误导你判断嵌套选择器是否可覆盖。
:not()、:is()、:where() 这些伪类怎么算?
它们本身不贡献权重,但括号里的内容照常计分:
-
div:not(.active)→.active算进c,div算进d→(0,0,1,1) -
:is(#main, .sidebar)→ 取内部最高权重项,即#main的b=1→ 整体为(0,1,0,0) -
:where(.btn, [disabled])→ 内部所有选择器权重强制归零 → 整体为(0,0,0,0),等效于通配符
容易踩的坑::not() 里写了个 ID,比如 div:not(#special),那 b 就变成 1 了,整条规则权重陡增,可能意外压过业务 class。
!important 不改变 specificity 数值,但改写裁决流程
!important 不是让 (0,0,1,0) 变成 (999,999,999,999),而是在层叠顺序中把它提到更高层级:
- 先分组:带
!important的声明一组,不带的另一组 - 同组内再比
specificity和源码顺序 - 所以
.foo { color: red !important; }会被#bar { color: blue !important; }覆盖(因为b=1 > c=1),但压不过style="color:green !important;"(a=1最高)
真正难缠的不是 !important 本身,而是它常出现在第三方组件或 reset.css 里,且不和业务代码同源——你没法靠“多写几个 class”覆盖它,必须用相同来源 + 更高 specificity + !important,或者干脆改用 CSS-in-JS / scoped style 等隔离方案。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











