跳到主要内容

CSS 选择器与优先级

问题

CSS 选择器有哪些类型?优先级是怎么计算的?伪类和伪元素有什么区别?

答案

选择器分类

基础选择器

选择器示例说明
通配符*匹配所有元素
元素选择器divp按标签名匹配
类选择器.class按 class 匹配
ID 选择器#id按 id 匹配
属性选择器[attr][attr="val"]按属性匹配

组合选择器

选择器示例说明
后代选择器div pdiv 内部所有 p(任意层级)
子选择器div > pdiv 的直接子元素 p
相邻兄弟div + p紧接 div 后面的第一个 p
通用兄弟div ~ pdiv 后面的所有同级 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未访问链接
:targetURL 锚点目标
: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)包含满足条件的子元素(父选择器
nth-child 用法
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 的背景层
伪类 vs 伪元素
  • 伪类:):选择元素的某种状态,不创建新元素(:hover:first-child
  • 伪元素::):创建虚拟元素并为其添加样式(::before::after

历史原因,::before 也可以写成 :before(单冒号),但推荐使用双冒号以区分。

优先级(Specificity)

优先级计算规则

CSS 优先级按 4 个等级 计算,通常表示为 (a, b, c, d)

等级权重来源
a如果是内联样式则为 1style="..."
bID 选择器个数#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

!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() 可以在不增加优先级的情况下组合选择器,非常适合写可被用户轻松覆盖的基础样式/组件库样式:

/* 组件库默认样式,优先级低 */
:where(.btn) {
padding: 8px 16px;
border-radius: 4px;
}
/* 用户只需一个类选择器就能覆盖 */
.btn { padding: 12px 24px; }

继承

部分 CSS 属性会从父元素继承到子元素:

可继承不可继承
colorwidth / height
font-*font-size 等)margin / padding
line-heightborder
text-aligndisplay
visibilityposition
cursorbackground
letter-spacingoverflow
word-spacingbox-sizing
list-stylefloat
/* 控制继承行为 */
.child {
color: inherit; /* 强制继承父元素 */
color: initial; /* 使用属性初始值 */
color: unset; /* 可继承属性 = inherit,不可继承 = initial */
color: revert; /* 恢复为浏览器默认样式 */
}

常见面试问题

Q1: CSS 优先级是怎么计算的?

答案

按 4 级权重 (内联, ID, 类/属性/伪类, 元素/伪元素) 逐级比较:

选择器计算优先级
div0,0,0,1最低
.class0,0,1,0
div.class0,0,1,1
#id0,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 → 这个赢 */

完整优先级排序:

  1. 用户样式表 !important
  2. 作者样式表 !important
  3. 作者样式表(内联 > ID > 类 > 元素)
  4. 用户样式表
  5. 浏览器默认样式
  6. 继承样式

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::aftercontent 属性有什么用?

答案

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 属性可以继承?

答案

简单记忆法则:与文字相关的属性通常可继承,与盒子相关的属性不可继承。

可继承:colorfont-*text-*line-heightletter-spacingword-spacingvisibilitycursorlist-style

不可继承:widthheightmarginpaddingborderdisplaypositionbackgroundoverflow

Q8: 如何重置/覆盖第三方组件库的样式?

答案

按策略排序(推荐到不推荐):

  1. 使用组件库提供的 API:如 CSS 变量、className props
  2. 提高选择器优先级
    .my-app .ant-btn { } /* 多加一层父类 */
  3. 使用 :where() 降低库的优先级(需要库支持)
  4. 使用 !important(最后手段)
  5. CSS Modules / CSS-in-JS:通过 scoped 样式避免冲突

Q9: 选择器的性能有差异吗?从右到左是什么意思?

答案

浏览器解析选择器是从右到左的。例如 .nav li a

  1. 先找到所有 <a> 元素
  2. 过滤出父元素是 <li>
  3. 再过滤出祖先有 .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+),可以在生产环境使用。

相关链接