先记住这个答案
优先级算法将选择器拆为 ID、CLASS、TYPE 三列,分别计数。ID 选择器贡献 1-0-0,类选择器贡献 0-1-0。比较时先看 ID 列,1 大于 0,所以 ID 规则胜出,无论类选择器有多少个。建议少用 ID,因为高优先级难以覆盖,增加维护成本。
- ID 选择器优先级为 1-0-0,类为 0-1-0。
- 优先级比较从左到右,ID 列优先。
- 少用 ID 可降低覆盖成本。
优先级数值比较的机制
浏览器将选择器分解为三个独立的计数列:ID、CLASS、TYPE。每个 ID 选择器(如 #nav)使 ID 列加 1;类选择器(如 .active)、属性选择器和伪类使 CLASS 列加 1;元素名和伪元素使 TYPE 列加 1。计算时不考虑顺序和数量上限,每列计数可以累积。
比较两个选择器的优先级时,先比较 ID 列,若不同则数值大者胜出;若相同再比较 CLASS 列,最后比较 TYPE 列。因此 #nav 为 1-0-0,.active 为 0-1-0,1>0,ID 一定赢。即便 #nav 没有其他列,而 .active 后面跟 100 个类型选择器,ID 列仍占绝对优势。
导航条高亮规则冲突场景
假设一个导航结构:<nav id="mainNav"><ul><li class="item active">首页</li></ul></nav>。基础样式 .item { color: gray; } 覆盖所有项目,欲高亮当前项,若用类选择器 .active { color: red; },由于同为 0-1-0,后定义者胜出,可正常生效。
但若误用了 ID 选择器 #mainNav .active,优先级变为 1-1-0,后续想用其他类规则(如 .active.dark)覆盖,需要 1-2-0 才能胜出,迫使开发者不断叠加类或改用 !important。实际处理时应坚持 .item、.active 这类类选择器,保持优先级在同一水平。
失效条件与工程代价
当同一条规则同时包含 ID 和类时,ID 列的高权重会让覆盖变得困难。例如第三方组件库绑定了 #app .widget,若团队后续想通过 .override 微调,必须写成 #app .widget.override 才能生效,否则会被组件的 1-1-0 压过。
避免方式是组件样式仅用类名,或者用 :where() 将组件规则优先级降为 0。若必须使用 ID,则要接受其全局不可覆盖性,并集中管理冲突点。对长期维护的项目,控制优先级在 0-1-0 以下能减少不确定的覆盖链。
容易答错的地方
- 认为类选择器数量可以超过ID
- 错误观点:当类选择器足够多时(如 .a.b.c.d)优先级会高于 ID。实际上比较是逐列进行的,ID 列只要大于 0,其他列再大也无济于事。计算规则规定 ID 列有最终决定权。
- 混淆 ID 和属性选择器
- 有人将
[id="x"]与#x等同。实际上#x增加 ID 列(1-0-0),而[id="x"]是属性选择器,只增加 CLASS 列(0-1-0)。两者在优先级的权重完全不同,不能混为一谈。
面试官还会怎么问?
如果两个选择器分别为 `#a .b` 和 `#a .c .d`,优先级如何?
前者为 1-1-0,后者为 1-2-0。比较时 ID 列相等,CLASS 列 2>1,所以后者胜出。但若前者带 !important,则即使优先级较低,它也会优先生效,因为 !important 属于更高的重要性分组。
ID 选择器与 `!important` 同时出现会怎样?
!important 提升声明到另一个重要性分组,优先级计算在重要声明内部进行。同组内 ID 规则通常赢过类规则,但 !important 本身不属于优先级列,不能叠加在数值上。
为何推荐使用类而非 ID 进行样式设计?
因为类可以复用,且优先级一致,覆盖时通常只需相同列数的比较。ID 要求文档唯一,一旦被定义就形成堡垒,后来的样式难以覆盖,也限制组件化。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。