跳到主要内容

CSS 优先级(特异性)怎么算:ID/类/标签权重与样式不生效排查

讲清 CSS 优先级如何按内联、ID、类、标签分列算分,为什么一个 ID 压过一百个类,!important 为什么跳出比较,以及样式不生效时该怎么一步步查到真凶。

发布于 作者 李雷
#CSS #前端 #优先级 #选择器

CSS 优先级(特异性)怎么算,以及样式为什么不生效

写 CSS 写久了,迟早会遇到一条让人血压升高的事:你明明写了一条规则,颜色就是不变。打开 devtools 一看,样式被划了一道删除线。这背后几乎都是同一件事在起作用:优先级,也叫特异性(specificity)。把它算明白,90% 的"样式不生效"当场就能定位。

优先级到底是一个怎样的数

很多人脑子里把优先级当成一个分数。这是误解。它其实是一组分列的计数,一般写成 (a, b, c) 三元组,外加一个更高的内联档:

  • 内联 style 属性:权重 1000,常单独写成 (1,0,0,0) 那一档。
  • ID 选择器(#header):a 列,权重 100。
  • 类 / 属性 / 伪类(.btn[type]:hover):b 列,权重 10。
  • 元素 / 伪元素(div::before):c 列,权重 1。
  • 通配符 * 和组合器(>+~、后代空格):一律不计。

记 1000 / 100 / 10 / 1 这套数只是方便心算。引擎真正做的是一列一列从左往右比:a 相同才看 b,b 相同才看 c。列与列之间从不进位。

一个 ID 真的压过一百个类

因为是一列一列比、不进位,所以结论有点反直觉但很硬:#x 是 (1,0,0),叠了一百个类的选择器是 (0,100,0)。先比 a 这一列,1 对 0,#x 直接赢下,后面 b 有多大都不看了。

那句流传很广的"256 个 class 会进位成一个 ID",只在某个上古 IE/Netscape 版本里短暂成立过,之后一直是以讹传讹。现代任何引擎都不进位。所以想靠多堆几个类去压过一个 ID,方向就错了,你需要的是自己也上一个 ID,或者把对方的 ID 降权。

算一个真实例子

拿这条选择器:#sidebar .link a:hover。逐项数一遍:

  • #sidebar → ID,a +1
  • .link → 类,b +1
  • a → 元素,c +1
  • :hover → 伪类,b +1

合起来是 (1, 2, 1)。如果同一个元素上还有一条 .link--muted,它是 (0, 1, 0)。两条放一起比:先比 a,1 对 0,第一条直接胜出,.link--muted 的颜色就被划掉了。这正是"我的样式怎么不生效"最常见的原型,胜负在第一列就分完了。

不想一个个手数的时候,我自己的习惯是直接把几条选择器各占一行丢进 CSS 优先级计算器,它会把每条算成三元组并高亮谁赢、是哪一位分出的胜负。有一次我和同事为一条 #app .card 争了十分钟,粘进去两秒就看到是 a 列(他那条带 ID)说了算,争论当场结束,比开 devtools 翻 cascade 还快。

!important 为什么是另一套规则

!important 不在 (a, b, c) 三元组里。它会跳出正常的优先级比较,不看三元组,直接压过任何不带 important 的声明。所以它确实很猛,但也正因为猛,容易留坑。

两条都带 !important 的时候,又退回正常规则:先比优先级,再比源码顺序,更靠后、更具体的那条 !important 仍然压过更早的。于是项目里一旦 !important 满天飞,你就会陷入"再加一个 important 盖过上一个"的循环。这就是所谓的优先级战争。

正确顺序是:先问能不能用一个更具体的选择器解决,实在不行再动 !important,而且最好只留给工具类这种确实需要无条件覆盖的场景。

样式不生效,按这个顺序查

遇到一条 CSS 没生效,别急着加 !important,按这条路走基本都能找到:

  1. 先看元素上有没有行内 style。它高过你在样式表里能写的任何选择器,包括 #id#id#id
  2. 再看有没有别的规则带 !important
  3. 都没有,就比两条规则的三元组,从 a 列往右看哪一列分出胜负。
  4. 三元组完全打平,那是源码顺序决定,后面的赢,把你的规则挪到后面即可。

顺手说两个降权和抬权的小技巧。想给某条规则降权又不动 HTML,用 :where() 把它的 ID 包起来,:where(#sidebar) .link 会算成 (0,1,0),就当那个 ID 不存在。想抬一点权重又不想上 !important,把类自己叠一遍,.btn.btn 算 (0,2,0)。

避免优先级战争

优先级战争的根子,几乎都是基础样式写得太重。组件库尤其要小心:任何高于 (0,1,0) 的默认值,都是将来某个使用方"不复制你的优先级就盖不掉"的工单。所以基础样式尽量压在单类一层,需要保持可覆盖的部分用 :where() 清零,真正需要锁死的才上 ID 或 !important

算清楚之后,顺手的活儿也轻松不少:写完规则可以用 CSS 格式化工具把缩进和分组理顺,review 时一眼看清每条选择器有多重。优先级从来不玄学,它只是一组一列一列、从不进位的计数。数清楚了,样式就不会再偷偷不生效。


Made by Toolora · Updated 2026-06-13