大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
选择器
这一课你将能做到
- 熟练使用元素、类、ID、属性、伪类、伪元素六类选择器
- 用组合器(后代、子代、兄弟)精确表达元素关系
- 手算任意两条选择器的特异性,预判谁会生效
为什么要学
选择器是 CSS 的「瞄准系统」。写 CSS 一半时间在写选择器:选不准就染错颜色,选太宽就误伤一片。面试里特异性计算是必考题。
人话:选择器就像快递地址——写得越精确,包裹送得越准;写「中国」就送错,写「北京市海淀区xx小区3栋501」才到位。
先建立图像
<style>
p { color: gray; } /* 元素 */
.highlight { background: yellow; } /* 类 */
#main { font-size: 20px; } /* ID */
a[target] { color: red; } /* 属性 */
li:first-child { font-weight: bold; } /* 伪类 */
p::first-line { text-transform: uppercase; } /* 伪元素 */
</style>
<div id="main">
<p class="highlight">第一段,类 highlight</p>
<p>第二段,<a href="#" target="_blank">带 target 的链接</a></p>
<ul><li>列表第一项</li><li>列表第二项</li></ul>
</div>
浏览器渲染后:
第一段,类 highlight ← 黄底、灰字、20px
第二段,带 target 的链接 ← 链接红色
• 列表第一项 ← 加粗
• 列表第二项
跟着做
步骤 1:基础三剑客
/* 元素选择器:选中所有该标签 */
p { color: #333; }
/* 类选择器:. 开头,最常用 */
.card { padding: 16px; }
/* ID 选择器:# 开头,页面唯一 */
#header { height: 60px; }
类名建议小写加连字符:.user-card、.btn-primary。
步骤 2:属性选择器
input[type="text"] { border: 1px solid #ccc; } /* 精确匹配 */
a[href^="https"] { color: green; } /* 以 https 开头 */
a[href$=".pdf"] { color: orange; } /* 以 .pdf 结尾 */
img[alt*="logo"] { border: none; } /* 包含 logo */
实战场景:表单样式按 type 区分、外链自动加图标。
步骤 3:伪类(描述元素状态)
a:hover { color: red; } /* 鼠标悬停 */
input:focus { outline: 2px solid blue; } /* 获得焦点 */
li:first-child { margin-top: 0; } /* 第一个子元素 */
li:last-child { margin-bottom: 0; } /* 最后一个 */
li:nth-child(2n) { background: #f5f5f5; } /* 偶数项,斑马纹 */
button:disabled { opacity: 0.5; } /* 禁用态 */
nth-child 接受 2n、2n+1、odd、even、具体数字。
步骤 4:伪元素(创建虚拟元素,用双冒号)
p::first-line { font-weight: bold; } /* 首行 */
p::first-letter { font-size: 2em; } /* 首字母,做首字下沉 */
.quote::before { content: "「"; }
.quote::after { content: "」"; }
::before 和 ::after 必须配合 content 才显示。
步骤 5:组合器
div p { } /* 后代:div 里面所有 p,无论多深 */
div > p { } /* 子代:div 的直接儿子 p */
h2 + p { } /* 相邻兄弟:紧跟 h2 的第一个 p */
h2 ~ p { } /* 通用兄弟:h2 后面所有同级 p */
写代码感受下:
<style>
.a p { color: blue; } /* 所有后代 p */
.a > p { color: red; } /* 直接子 p */
</style>
<div class="a">
<p>直接子 p(红)</p>
<section><p>孙子 p(蓝)</p></section>
</div>
步骤 6:特异性计算
把选择器拆成 (ID数, 类/属性/伪类数, 元素/伪元素数),逐位比较:
p → (0,0,1)
.card → (0,1,0)
#header → (1,0,0)
div .card p → (0,1,2)
#nav .item:hover → (1,2,0)
* → (0,0,0)
(1,2,0) 比 (0,10,5) 大——左边一位压死后面所有。
讲清楚要点
- 类选择器是主力:复用性最好,特异性适中,工程上 90% 用类。
- ID 选择器慎用:特异性太高,难覆盖;一般留给 JS 锚点。
- 伪类是状态,伪元素是结构:
:hover是「鼠标悬停的状态」,::before是「凭空造一个盒子」。 - 组合器从右往左读:
div > p浏览器先找所有 p,再看爸爸是不是 div。 - 特异性相同,后写的赢:跟优先级一样,规则顺序也很关键。
你可能会懵的地方
-
为什么有时候加类没用? 被更高特异性规则压住了。F12 看 Styles 面板,被划掉的就是输家。
-
:first-child和:first-of-type区别? 前者要求元素是爸爸的「第一个孩子」(任何标签);后者要求是「同类型里的第一个」。 -
::before还是:before? CSS3 规范伪元素用双冒号::,单冒号是历史兼容写法。现代代码用双冒号。 -
属性选择器性能差? 现代浏览器差距可忽略,但避免在超大列表用
[attr*="..."]这种带通配符的。 -
能用多个类吗? 能,
<div class="card primary">,CSS 里.card.primary表示「同时具有两个类」(中间无空格)。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
.card .title 写成 .card.title | 空格变后代、无空格变同时具有 | 想清楚要不要空格 |
p:first-child() 加括号 | 不是函数 | 直接 p:first-child |
::before 没写 content | 不显示 | 必须 content: "" 起步 |
给 ID 选择器加类 .a#b | 无意义且混乱 | 用 ID 或类,别混 |
写 div.container div.item div.text 嵌套过深 | 难维护、性能差 | 用扁平类名 .text |
卡点急救
- 样式不生效:F12 → Elements → 选中元素 → 看 Styles 面板,被划掉的规则就是特异性输了。
:hover不工作:先确认元素本身可见(有宽高、没被遮挡);移动端没有悬停,要用:active或:focus。nth-child数错:记住它是「在兄弟里的位置」,不是「在过滤后的位置」。
面试常问
-
优先级计算规则?
(inline, id, class/attr/pseudo-class, element/pseudo-element)四元组逐位比较,!important最高,inline 次之。 -
伪类和伪元素区别? 伪类描述已有元素的状态(
:hover、:nth-child),伪元素创造新的虚拟元素(::before、::first-line)。 -
CSS 选择器从右往左匹配为什么? 性能优化。先取最右边的 key selector 找候选集,再向左逐级过滤,避免从左往右扫大量不匹配的分支。
自测三问
#nav .item:hover和div .list .item谁优先级高?div > p和div p有什么区别?分别匹配哪些元素?::before和:before写法哪个是规范?
小结一句话
选择器是 CSS 的瞄准器,掌握六类基础选择器加四种组合器,再配上特异性规则,就能精准命中任何元素。
延伸
- MDN CSS 选择器
- 下一课:单位与函数,搞清楚
rem和em的区别。