前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库CSS ID vs class specificity
CSS层叠与选择器

为什么一个 ID 选择器就能赢过好几个类选择器?

CSS 优先级分三列,从左到右是 ID、类、元素。比较时先看 ID 列,有 1 就赢过 0,后面的列不用比了。所以 #nav 的 (1, 0, 0) 比 .a.b.c.d 的 (0, 4, 0) 高,无论类写多少个都跨不过 ID 这一列。

前端进阶之旅 · 一题精讲更新于 2026.09.29
CSS#层叠与选择器
先看核心答案读代码示例
理解线索

三列从左到右比大小

  1. ID 列#name 每出现一个加 1,在最左边,权重最高
  2. 类/伪类列.class 和 [attr] 以及 :hover 每个加 1,排在第二列
  3. 元素列div 和 ::before 每个加 1,排在第三列,权重最低

三列独立比较,不进位。(0, 20, 0) 仍然输给 (1, 0, 0)。

核心回答

先记住这个答案

CSS 优先级体系把选择器分成三列。从左到右分别是 ID、类(含属性和伪类)、元素(含伪元素)。比较两个选择器时先看最左边的 ID 列,数字大的直接赢,不用再看后面的列。只有 ID 列相等才比类列,类列也相等才比元素列。这就是为什么一个 #nav 可以赢过任意多个 .class——因为 (1, 0, 0) 的 ID 列是 1,而 (0, n, 0) 的 ID 列是 0,比较到 ID 列就结束了。

  • ID 列高于类列,不看数量
  • #id 计入第一列,[id=x] 计入第二列
  • 三列之间不存在进位
  • 实际项目尽量少用 #id 选择器

一个 ID 赢过一排类

日常开发中经常遇到这样的场景:给一个元素写了一堆类选择器来描述样式,结果被另一条只用了 ID 选择器的规则覆盖。

ID 和类在不同列CSS
/* 优先级 (1, 0, 0),只有一个 #id */
#nav {
  color: red;
}

/* 优先级 (0, 4, 0),四个类叠加 */
.header .main .list .link {
  color: blue;
}

/* 最终 #nav 匹配到的元素显示红色 */

这两条规则如果作用在同一个元素上,红色赢。因为比较只看 ID 列:#nav 是 1,四个类加起来在 ID 列仍然是 0。就算把类叠加到 100 个,在 ID 列为 0 的情况下永远输给 ID 列为 1 的规则。 很多人以为优先级是一个十进制数,比如把 (0, 4, 0) 理解成 40,觉得够大了就能赢。实际上三列是独立比较的,不存在进位。这和 IP 地址每段独立类似,0.255.255 永远小于 1.0.0。

实际项目里怎么控制 ID 优先级

正因为 ID 的优先级太高,在实际项目中反而应该少用 #id 选择器。一旦用了,后续想覆盖就只能加 !important 或者写更长的 ID 选择器链,维护成本直线上升。

大多数 CSS 方法论(BEM、OOCSS、Tailwind)都建议只用类选择器。需要唯一标识元素时用 id 属性做锚点或 JavaScript 选取,但在 CSS 样式规则中用 .class 代替 #id。

如果已经有一条 #id 规则想低成本覆盖,有几个办法。把覆盖声明也写成 #id 选择器并放在后面,来源顺序会让后面的赢。或者用 #id.class 把优先级提到 (1, 1, 0)。最后的兜底是 !important,但这会开启另一场优先级军备竞赛,尽量避免。

另一种思路是用 @layer 控制层级。把第三方库的样式包进先声明的层,自己的业务样式放在后声明的层或不入层,这样即使第三方库内部用了 #id,层叠层的顺序也会让业务样式优先。

回答前,多想一步

容易答错的地方

把 [id=nav] 当 ID 选择器
[id=nav] 匹配的元素和 #nav 一样,但 [id=nav] 是属性选择器,优先级是 (0, 1, 0) 而不是 (1, 0, 0)。利用这个差异,可以通过 [id=nav] 引用 ID 又不抬高优先级。
以为加 !important 就等于提高优先级
!important 不是在三列里加了什么。它把声明提升到一个更高的层叠层级:所有 !important 声明先内部按优先级排名,再整体赢过所有普通声明。两个都带 !important 的声明还是要比三列优先级。
试着用自己的话回答

面试官还会怎么问?

内联样式在三列里排第几?

内联样式不在选择器三列里,它是写在 HTML 的 style 属性上的,优先级天然高于任何选择器规则。想覆盖内联样式,要么用 !important,要么用 JavaScript 直接改 style。

怎么在不提高优先级的前提下覆盖样式?

用 @layer 把基础样式放进先声明的层,业务覆盖样式放在后声明的层或直接不入层。层的声明顺序优先于选择器优先级。另一种做法是用 :where() 包裹选择器,它的优先级固定为零。

:not() 和 :is() 会影响 ID 列的优先级吗?

会。:not(#x) 和 :is(#x) 的优先级取参数中最高那个选择器的权重,如果参数里有 #id,就在 ID 列加 1。:where() 是例外,不管参数是什么,整体优先级都是零。

从一道题,走向一组知识

把知识连起来

层叠与选择器

选择器 ul#nav li.active a:hover 的优先级是多少?

用 ul#nav li.active a:hover 练习三列计数,巩固拆解过程。

层叠与选择器

两个相同优先级的规则发生冲突时,CSS 如何决定最终采用哪一条?

优先级相等时 CSS 靠来源顺序和层叠层打破平局,和优先级高低是两套规则。

参考资料

  • Specificity

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 一个 ID 赢过一排类
  3. 实际项目里怎么控制 ID 优先级
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑