先记住这个答案
该选择器的优先级由三部分组成:ID 选择器 #nav 计一个 ID(1-0-0);类选择器 .active 和伪类 :hover 各计一个类级别(0-1-0),共两个;元素选择器 ul、li、a 共三个,各计一个元素,因此 TYPE 列加 3。最终数值为 1-2-3。权威计算为 ID-类-元素。
- ID 选择器计入 ID 列
- 类、属性、伪类计入 CLASS 列
- 元素和伪元素计入 TYPE 列
逐项计数:拆解选择器
优先级是浏览器在层叠中决定哪个声明生效的关键,它只考虑选择器的组成部分。标准算法按 ID、CLASS、TYPE 三列计数。ID 列包含 #id;CLASS 列包含 .class、属性选择器和伪类如 :hover;TYPE 列包含元素名如 ul 和伪元素。本选择器中,#nav 位于 ID 列,.active 和 :hover 一起为 CLASS 列加 2,三个元素 ul、li、a 让 TYPE 列加 3,因此表示为 1-2-3。
这里的计数不考虑选择器中间的空格或组合符,也不考虑选择器长度。每一列是独立的,比较时先比 ID,再比 CLASS,最后比 TYPE。ul#nav li.active a:hover 与另一个只有两个 ID 的选择器竞争时,后者 ID 列更大从而胜出。
导航高亮与悬停冲突
假设页面上有规则 A:ul#nav li.active a { color: red; } 和规则 B:ul#nav li a:hover { color: blue; }。当鼠标悬停在 active 列表项内的链接上时,两条规则都匹配。A 的优先级是 1-1-3(1 个 ID,1 个类 .active,3 个元素 ul、li、a),B 的优先级是 1-1-3(1 个 ID,1 个伪类 :hover,3 个元素 ul、li、a),二者完全相同。由于优先级相同,最终颜色取决于它们在样式表中的先后顺序,后出现的规则会生效。不能简单断言总是红色或蓝色;若希望悬停时必定覆盖,需要提高 B 的选择器优先级。
若希望悬停时显示蓝色,就必须提高 B 的选择器优先级,例如写成 ul#nav li.active a:hover,此时 B 的优先级变成 1-2-3,高于 A 的 1-1-3,于是悬停变蓝生效。这展示了理解计数细节如何直接影响设计意图。工程中相同逻辑的规则在不同结构下容易埋坑,推荐使用 BEM 等类名方案降低 ID 使用频率,减少这类意外覆盖。
适用边界与陷阱
该选择器不是选择后代,而是精确匹配:ul#nav 必须是同一个 ul 元素且带 ID,li.active 是后代,a:hover 是更深的后代。只要任一部分不匹配,整个规则不生效。另外,此规则不解决继承冲突:若子元素未直接命中,则继承父级样式,与优先级无关。
优先级比较只在同源同层且同重要级别内有意义。若涉及 !important 或 CSS 层叠层,顺序会重新洗牌,优先级不再是决定性因素。书写时应避免用大量 ID 堆积,因为难以覆盖;优先使用类选择器,并通过层叠层控制基础样式,以便在需要时轻松扩展。
容易答错的地方
- 将通配符或组合符计入优先级
- 有人认为
ul#nav li.active a:hover中的空格或>会增加权重,其实通配符*、后代空格、子元素>、同级+等都没有权重,不影响 ID、CLASS、TYPE 的计数。只有简单选择器本身会累加数值。 - 把伪类算作元素列
- 常有人将
:hover当成类似元素或伪元素,计入 TYPE 列。实际上标准把伪类归入 CLASS 列,与类、属性并列。例如a:hover的优先级是0-1-1,而a::before是0-0-2,二者性质不同。
面试官还会怎么问?
`:is()` 和 `:not()` 的优先级是怎么处理的?
根据规范,:not() 自身的权重为零,但优先级取参数中最高的那个;:is() 同取参数最大,:where() 则整体为零。例如 :is(li, .active) 的优先级等于 .active 即 0-1-0,而非 0-1-0 加上伪类本身。
两个优先级相同的规则,最后哪个生效?
在级联中,若优先级相同,则位于后面的规则胜出(来源和层相同时)。这称为“后来居上”,经常利用此原理在样式表底部放置覆盖规则。不过在组件化时代,更推荐用层叠层显式控制,而非依赖源码顺序。
内联样式和 `!important` 怎么参与比较?
内联样式在优先级上视为 1-0-0 之上的最高级别,但低于带 !important 的规则;而 !important 会反转层叠顺序,优先于普通声明,但在同一重要性级别中仍会比较高优先级。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。