CSS 选择器与优先级
问题
CSS 选择器有哪些类型?优先级是怎么计算的?伪类和伪元素有什么区别?
答案
选择器分类
基础选择器
| 选择器 | 示例 | 说明 |
|---|---|---|
| 通配符 | * | 匹配所有元素 |
| 元素选择器 | div、p | 按标签名匹配 |
| 类选择器 | .class | 按 class 匹配 |
| ID 选择器 | #id | 按 id 匹配 |
| 属性选择器 | [attr]、[attr="val"] | 按属性匹配 |
组合选择器
| 选择器 | 示例 | 说明 |
|---|---|---|
| 后代选择器 | div p | div 内部所有 p(任意层级) |
| 子选择器 | div > p | div 的直接子元素 p |
| 相邻兄弟 | div + p | 紧接 div 后面的第一个 p |
| 通用兄弟 | div ~ p | div 后面的所有同级 p |
| 分组选择器 | h1, h2, h3 | 匹配多个选择器 |
属性选择器
[href] /* 有 href 属性 */
[type="text"] /* type 完全等于 "text" */
[class~="btn"] /* class 包含完整单词 "btn" */
[lang|="en"] /* lang 等于 "en" 或以 "en-" 开头 */
[href^="https"] /* href 以 "https" 开头 */
[href$=".pdf"] /* href 以 ".pdf" 结尾 */
[href*="example"] /* href 包含 "example" */
[type="text" i] /* 不区分大小写匹配 */
伪类选择器
状态伪类:
| 伪类 | 说明 |
|---|---|
:hover | 鼠标悬停 |
:active | 鼠标按下 |
:focus | 获得焦点 |
:focus-visible | 键盘聚焦时显示焦点(推荐代替 :focus) |
:focus-within | 子元素获得焦点 |
:visited | 已访问链接 |
:link | 未访问链接 |
:target | URL 锚点目标 |
:checked | 选中状态 |
:disabled / :enabled | 禁用/启用状态 |
:required / :optional | 必填/可选 |
:valid / :invalid | 验证通过/失败 |
:placeholder-shown | 显示 placeholder 时 |
:empty | 没有子元素 |
结构伪类:
| 伪类 | 说明 |
|---|---|
:first-child | 第一个子元素 |
:last-child | 最后一个子元素 |
:nth-child(n) | 第 n 个子元素 |
:nth-last-child(n) | 倒数第 n 个子元素 |
:nth-of-type(n) | 同类型中的第 n 个 |
:first-of-type | 同类型中的第一个 |
:last-of-type | 同类型中的最后一个 |
:only-child | 唯一子元素 |
:only-of-type | 同类型中唯一的 |
:not(selector) | 否定选择器 |
:is(selector) | 匹配列表中任一选择器 |
:where(selector) | 同 :is(),但优先级为 0 |
:has(selector) | 包含满足条件的子元素(父选择器) |
li:nth-child(2n) /* 偶数行 */
li:nth-child(odd) /* 奇数行 */
li:nth-child(3n+1) /* 1, 4, 7, 10... */
li:nth-child(-n+3) /* 前 3 个 */
伪元素选择器
| 伪元素 | 说明 |
|---|---|
::before | 在元素内容前插入内容 |
::after | 在元素内容后插入内容 |
::first-line | 元素的第一行文本 |
::first-letter | 元素的首字母 |
::placeholder | 输入框 placeholder |
::selection | 选中的文本 |
::marker | 列表项标记 |
::backdrop | 全屏/dialog 的背景层 |
- 伪类(
:):选择元素的某种状态,不创建新元素(:hover、:first-child) - 伪元素(
::):创建虚拟元素并为其添加样式(::before、::after)
历史原因,::before 也可以写成 :before(单冒号),但推荐使用双冒号以区分。
优先级(Specificity)
优先级计算规则
CSS 优先级按 4 个等级 计算,通常表示为 (a, b, c, d):
| 等级 | 权重 | 来源 |
|---|---|---|
| a | 如果是内联样式则为 1 | style="..." |
| b | ID 选择器个数 | #id |
| c | 类/属性/伪类选择器个数 | .class、[attr]、:hover |
| d | 元素/伪元素选择器个数 | div、::before |
比较规则:从 a 到 d 逐级比较,高等级大则优先,相同则比较下一级。
* /* (0,0,0,0) */
div /* (0,0,0,1) */
div p /* (0,0,0,2) */
.class /* (0,0,1,0) */
div.class /* (0,0,1,1) */
#id /* (0,1,0,0) */
#id .class /* (0,1,1,0) */
div#id .class p /* (0,1,1,2) */
style="..." /* (1,0,0,0) */
优先级总排序
!important > 内联样式 > ID > 类/属性/伪类 > 元素/伪元素 > 通配符/继承
!important 优先级最高,会覆盖一切:
.box {
color: red !important; /* 最高优先级 */
}
除非另一个 !important 的选择器优先级更高。滥用 !important 会导致样式难以维护,应尽量避免。
特殊选择器的优先级
| 选择器 | 优先级 |
|---|---|
:is(.a, .b) | 取参数中最高的优先级 |
:where(.a, .b) | 始终为 0 |
:not(.a) | 参数的优先级 |
:has(.a) | 参数的优先级 |
* | (0,0,0,0) |
组合选择器 , | 各自独立计算 |
:is(#id, .class) .box { } /* 优先级 = (0,1,1,0) 因为 #id 最高 */
:where(#id, .class) .box { } /* 优先级 = (0,0,1,0) 因为 :where 为 0 */
:where() 可以在不增加优先级的情况下组合选择器,非常适合写可被用户轻松覆盖的基础样式/组件库样式:
/* 组件库默认样式,优先级低 */
:where(.btn) {
padding: 8px 16px;
border-radius: 4px;
}
/* 用户只需一个类选择器就能覆盖 */
.btn { padding: 12px 24px; }
继承
部分 CSS 属性会从父元素继承到子元素:
| 可继承 | 不可继承 |
|---|---|
color | width / height |
font-*(font-size 等) | margin / padding |
line-height | border |
text-align | display |
visibility | position |
cursor | background |
letter-spacing | overflow |
word-spacing | box-sizing |
list-style | float |
/* 控制继承行为 */
.child {
color: inherit; /* 强制继承父元素 */
color: initial; /* 使用属性初始值 */
color: unset; /* 可继承属性 = inherit,不可继承 = initial */
color: revert; /* 恢复为浏览器默认样式 */
}
常见面试问题
Q1: CSS 优先级是怎么计算的?
答案:
按 4 级权重 (内联, ID, 类/属性/伪类, 元素/伪元素) 逐级比较:
| 选择器 | 计算 | 优先级 |
|---|---|---|
div | 0,0,0,1 | 最低 |
.class | 0,0,1,0 | |
div.class | 0,0,1,1 | |
#id | 0,1,0,0 | |
style="..." | 1,0,0,0 | |
!important | — | 最高 |
高等级的 1 个权重单位 > 低等级的无穷多个。10 个类选择器也赢不了 1 个 ID 选择器。
Q2: !important 的优先级规则是什么?
答案:
多个 !important 之间仍然比较选择器优先级:
.box { color: red !important; } /* 0,0,1,0 + !important */
#id { color: blue !important; } /* 0,1,0,0 + !important → 这个赢 */
完整优先级排序:
- 用户样式表
!important - 作者样式表
!important - 作者样式表(内联 > ID > 类 > 元素)
- 用户样式表
- 浏览器默认样式
- 继承样式
Q3: 伪类和伪元素的区别?
答案:
| 特性 | 伪类(Pseudo-class) | 伪元素(Pseudo-element) |
|---|---|---|
| 语法 | :hover(单冒号) | ::before(双冒号) |
| 作用 | 选择元素的状态 | 创建虚拟元素 |
| 是否创建 DOM | 否 | 是(虚拟 DOM 节点) |
| 数量 | 可以叠加多个 | 同类型只能一个 |
| 示例 | :hover、:nth-child | ::before、::after |
Q4: :nth-child 和 :nth-of-type 的区别?
答案:
<div>
<p>段落1</p>
<span>行内1</span>
<p>段落2</p>
<p>段落3</p>
</div>
p:nth-child(2) { } /* 不匹配!第 2 个子元素是 <span> 不是 <p> */
p:nth-of-type(2) { } /* 匹配 "段落2"(所有 <p> 中的第 2 个) */
:nth-child(n)— 先找第 n 个子元素,再验证是否匹配前面的选择器:nth-of-type(n)— 先筛选出同类型元素,再从中取第 n 个
Q5: :is()、:where()、:has() 分别是什么?
答案:
/* :is() — 匹配列表中任一选择器,优先级取最高参数 */
:is(h1, h2, h3) { color: blue; }
/* 等价于 h1, h2, h3 { color: blue; } 但更易维护 */
/* :where() — 同 :is(),但优先级始终为 0 */
:where(h1, h2, h3) { color: blue; }
/* 适合写低优先级的基础样式 */
/* :has() — 父选择器,CSS 中第一个真正的"向上"选择 */
.card:has(img) { padding: 0; } /* 包含 img 的 .card */
.card:has(> h2) { border: 1px solid; } /* 直接子元素有 h2 的 .card */
/* :has() 也可以做兄弟选择 */
h2:has(+ p) { margin-bottom: 0; } /* 后面紧跟 p 的 h2 */
Q6: ::before 和 ::after 的 content 属性有什么用?
答案:
content 是伪元素必需的属性,定义插入的内容:
.required::after {
content: '*'; /* 文本 */
content: ''; /* 空内容(用于纯装饰) */
content: attr(data-tip); /* 读取属性值 */
content: counter(step); /* CSS 计数器 */
content: url(icon.svg); /* 图片(不推荐) */
content: open-quote; /* 引号 */
}
注意:
- 伪元素without
content不会渲染 content产生的文本无法被选中,搜索引擎也不索引- 屏幕阅读器可能会读取
content内容
Q7: 哪些 CSS 属性可以继承?
答案:
简单记忆法则:与文字相关的属性通常可继承,与盒子相关的属性不可继承。
可继承:color、font-*、text-*、line-height、letter-spacing、word-spacing、visibility、cursor、list-style
不可继承:width、height、margin、padding、border、display、position、background、overflow
Q8: 如何重置/覆盖第三方组件库的样式?
答案:
按策略排序(推荐到不推荐):
- 使用组件库提供的 API:如 CSS 变量、className props
- 提高选择器优先级:
.my-app .ant-btn { } /* 多加一层父类 */ - 使用
:where()降低库的优先级(需要库支持) - 使用
!important(最后手段) - CSS Modules / CSS-in-JS:通过 scoped 样式避免冲突
Q9: 选择器的性能有差异吗?从右到左是什么意思?
答案:
浏览器解析选择器是从右到左的。例如 .nav li a:
- 先找到所有
<a>元素 - 过滤出父元素是
<li>的 - 再过滤出祖先有
.nav的
这是因为从右到左可以更快排除不匹配的元素。性能影响排序:
- ID 选择器:最快
- 类选择器:快
- 元素选择器:中等
- 通配符/属性选择器:慢
- 复杂后代选择器:最慢
但现代浏览器的选择器引擎已经非常高效,除非有极大量 DOM 节点,否则性能差异可忽略。更多性能优化参见 CSS 性能优化。
Q10: 如何用纯 CSS 选择"父元素"?
答案:
使用 :has() 伪类(CSS 中第一个真正的父选择器):
/* 选择包含 .error 子元素的 .form-group */
.form-group:has(.error) {
border-color: red;
}
/* 选择包含 checked checkbox 的 label */
label:has(input:checked) {
background: #e0f0ff;
}
/* 选择包含图片的卡片 */
.card:has(img) {
padding: 0;
}
:has() 的兼容性已经非常好(Chrome 105+、Firefox 121+、Safari 15.4+),可以在生产环境使用。