前端阶梯——第八章、CSS入门与选择器

上一章结尾,你写出了人生第一个"结构规范"的新闻页。但它长这样:黑字白底、宋体字号、所有东西从上往下堆------能看,但不好看。

问题出在:HTML 只负责"有什么",不负责"好不好看"。

这一章,我们开始给页面穿上衣服。

但别急着学"怎么变红变蓝"。CSS 真正难的地方,从来不是"怎么写",而是**"为什么我写了却不生效"** 。几乎每个前端新手都经历过这样的时刻:明明写了 color: red,字就是不红。

这一章要解决的正是这件事。学完本章,你能:

  • 知道样式该写在哪里(以及写在哪会影响优先级);
  • 熟练用选择器精确点中你想改的元素;
  • 最重要的是------当两条规则打架时,能一眼说出谁赢。

最后这条,才是 CSS 名字里那个 C (Cascading,层叠)的真正含义。

8.1 CSS 是什么:把一条规则拆开看

CSS = Cascading Style Sheets(层叠样式表)。 三个词,一个一个拆:

词 含义
Style Sheets(样式表) 它是一张"外观清单":谁该是什么颜色、多大字号、摆在哪里
Cascading(层叠) 当多条规则都指向同一个元素时,浏览器按一套固定算法决定谁生效

一句话概括两者的分工:

HTML 描述"有什么",CSS 描述"长什么样"。 两者分开写,是前端工程化的第一块基石------改外观不动结构,改结构不动外观。

现在看一条 CSS 规则长什么样:

css 复制代码
/* 选择器:选谁 */  p {
  color: red;          /* 声明一:属性 + 冒号 + 值 + 分号 */
  font-size: 18px;     /* 声明二 */
}                      /* 整个大括号叫"声明块" */

拆成四个组成部分:

名称 上面的例子 作用
选择器(selector) p 决定"给谁加样式"
声明块(declaration block) { ... } 装着一到多条声明
声明(declaration) color: red; 一条具体的样式
属性 / 值 color / red 改哪一项 / 改成什么

这四个小地方,新手几乎都会踩一次:

css 复制代码
/* ❌ 错:冒号写成了等号 */
p { color = red; }

/* ❌ 错:值和单位之间加了空格 */
p { font-size: 18 px; }

/* ❌ 错:最后一条声明漏了分号(下一条会被吞掉) */
p {
  color: red
  font-size: 18px;   /* 这一条直接失效 */
}

/* ✅ 对 */
p {
  color: red;
  font-size: 18px;
}

⚠️ 注意:CSS 报错不弹窗。 JavaScript 出错会在控制台看到红色的报错,但 CSS 写错了,浏览器只会安静地"忽略这一条" ,页面照常显示,只是样式没生效。这就是为什么初学者常常"找不到自己错在哪"------没有报错,就是最难的报错 。 排查方法:按 F12 打开 DevTools,在 Elements → Styles 面板里看。写错的声明会被划掉或标黄,一眼就能发现。

8.2 样式写在哪里:三种引入方式

CSS 代码可以放在三个地方。它们不只是"位置不同",优先级也不同------这是本章第一个重点。

方式一:行内样式 ------直接写在标签的 style 属性里。

html 复制代码
<p style="color: red; font-size: 18px;">这段文字是红色的。</p>

方式二:内部样式表 ------写在 <head> 里的 <style> 标签中。

html 复制代码
<head>
  <style>
    p { color: red; }
    .title { font-size: 24px; }
  </style>
</head>

方式三:外部样式表 ------单独一个 .css 文件,用 <link> 引入。

html 复制代码
<head>
  <link rel="stylesheet" href="style.css">
</head>
css 复制代码
p { color: red; }
.title { font-size: 24px; }

💡 提示:<link> 的两个属性别写错。 rel="stylesheet" 是固定的(告诉浏览器"这是个样式表"),href 指向文件路径。顺序不能反,名字不能拼错,否则样式完全不加载 ,而且控制台不会报错------这是新手最常见的"样式全丢了"的原因。

三种方式怎么选?一张表看明白:

引入方式 作用范围 复用性 适用场景
行内样式 单个标签 ✘ 完全不能复用 基本不用。只在 JS 动态改单个元素、或调试时临时试
内部样式表 当前页面 ▲ 仅本页可用 单页小 demo、学习时快速实验
外部样式表 可被多个页面引入 ✔✔ 一个文件全站共用 实际项目的标准做法

优先级的真相(这里很多教程都写错了)

你一定听过这个说法:

"行内样式 > 内部样式 > 外部样式。"

这个说法有一半是错的。 真实规则是:

⚠️ 纠偏:内部样式和外部样式"权重相同",谁后加载谁赢。 浏览器把它们一视同仁 ,都叫"作者样式表"(author stylesheet)。当两条规则权重相同时,后出现的那条生效 (这叫"源顺序")。 所以:<style> 写在 <link> 后面 ,内部样式赢;写在 <link> 前面 ,外部样式赢。它们之间没有天生的高低。

完整、准确的优先级顺序是这样的:

顺序(从低到高) 说明
1. 外部样式表 / 内部样式表 同一档 。权重相同时,看谁在文档里后出现
2. 行内样式(style="...") 普通声明里它最高------因为它离元素最近
3. 样式表里带 !important 的声明 能盖过普通的行内样式
4. 行内样式里带 !important 的声明 作者样式的最高级别

🔍 一个容易被忽略的细节:!important 能打败行内样式。 很多老教程说"行内样式优先级最高",这是不严谨的。准确说法是:行内样式在"普通声明"里最高;但样式表里任何一条带 !important 的规则,都能盖过它。 不过请记住------!important 是急救药,不是保健品。它强行改变优先级,会让后面的样式更难预测。8.6 节我们细说它的代价。

场景选择建议:

场景 推荐 原因
多页面网站 外部样式表 一个文件全站共用,改一处全站生效,还能被浏览器缓存
单页小练习 内部样式表 一个文件搞定,不用管路径
用 JS 动态改某个元素 行内样式(el.style) 只影响这一个元素,立即生效
想快速试一个效果 行内(然后搬进样式表) 试完记得搬走,别留在正式代码里

8.3 基础选择器:怎么"点中"目标

样式写好了,接下来是核心问题:怎么选中你想改的那些元素? 这就是选择器的活儿。

先看最常用的五个:

选择器 写法 例子 命中什么 优先级权重
标签(元素) 标签名 p { } 页面上所有 <p> 低
类 . + 类名 .card { } 所有 class="card" 的元素 中
ID # + id #app { } 唯一的 id="app" 的元素 高
通配 * * { } 所有元素 0
属性 [属性] [disabled] { } 带该属性的元素 中
css 复制代码
/* 标签选择器:所有段落 */
p { line-height: 1.8; }

/* 类选择器:所有带 class="card" 的元素(最常用) */
.card { border: 1px solid #ddd; border-radius: 8px; }

/* ID 选择器:id 是唯一的,一页只能有一个 */
#app { max-width: 900px; }

/* 通配选择器:所有元素(通常只用来做全局重置) */
* { box-sizing: border-box; }

/* 属性选择器:带 type="text" 的输入框 */
input[type="text"] { border: 1px solid #ccc; }

HTML 里怎么配合?注意 class 和 id 的写法差别:

html 复制代码
<p class="card">我是卡片</p>
<div id="app">页面根容器</div>
<h2 class="card big">可以同时用多个类</h2>

🔍 为什么实际项目里"类选择器"用得最多? 三个原因:

  1. 可复用 ------一个 .card 能套在无数个元素上,样式写一次到处用;
  2. 语义清晰 ------.card 一看就知道是"卡片",比 div 有意义;
  3. 优先级适中 ------不高不低,好覆盖,不会动不动就要 !important 去打架。

这条经验非常值钱:id 是给 JavaScript 用的"挂钩",不是给 CSS 用的选择器 。给元素加 id 是为了让 JS 能找到它;拿 id 去写样式,会让优先级飙到最高,后面对付它很痛苦(8.6 节会看到为什么)。

8.4 关系选择器:根据"位置"来选

除了按"身份"选,CSS 还能按元素之间的位置关系来选。这是选择器里最能体现"级联"威力的部分。

html 复制代码
<nav>
  <a href="/">首页</a>          <!-- nav 的直接子元素 -->
  <span>
    <a href="/a">子元素里的链接</a>   <!-- nav 的后代,但不是直接子元素 -->
  </span>
</nav>
<h1>标题</h1>
<p>第一段</p>
<p>第二段</p>
选择器 写法 含义 例子
后代 A B(空格) A 里面所有层级的 B nav a → nav 里任何位置的 a(两个都命中)
子代 A > B A 的直接子元素里的 B nav > a → 只有首页那个 a(子元素里的不算)
相邻兄弟 A + B 紧跟在 A 后面的那一个 B h1 + p → 只有"第一段"
通用兄弟 A ~ B A 后面所有同级的 B h1 ~ p → 第一段、第二段都命中
并集 A, B 满足 A 或 B h1, h2 { } → h1 和 h2 都改
css 复制代码
/* 后代:导航里所有的链接 */
nav a { color: #333; text-decoration: none; }

/* 子代:只改 nav 的直接子级链接(更精确,不误伤) */
nav > a { font-weight: bold; }

/* 相邻兄弟:紧跟标题后的第一段,去掉上边距 */
h1 + p { margin-top: 0; }

/* 通用兄弟:标题之后的所有段落 */
h1 ~ p { color: #555; }

/* 并集:层级标题共用一套字号 */
h1, h2, h3 { font-family: "Microsoft YaHei", sans-serif; }

💡 提示:> 和空格差在哪? nav a(空格)会命中 nav 内部所有层级 的 <a>,哪怕它藏在 nav > span > div > a 里; nav > a(大于号)只命中最外一层的直接子元素 。 什么时候用 >?当你担心"样式误伤到嵌套更深的元素"时 。比如导航栏里的下拉菜单里也有链接,你可能只想改最外层那一排------这时 > 就是保险。
⚠️ 注意:+ 和 ~ 只认"后面的同级兄弟"。 A + B 里的 B 必须是 A 之后 的、同一个父元素下 的兄弟。写在 A 前面的兄弟不算,不是同级的(比如 A 的子元素)也不算。这两个选择器日常用得少,但读别人代码时会遇到,得认得。

8.5 伪类与伪元素:选中"状态"和"看不见的部分"

有两类元素,普通选择器永远选不到:

  1. 同一个元素的不同状态------鼠标悬停时的按钮、获得焦点的输入框;
  2. 元素身上不存在的"部件"------段落的第一行、元素前方那个装饰小箭头。

它们分别靠伪类 和伪元素来选。

伪类(pseudo-class) :选中元素的某个状态 ,单冒号 : 开头。

伪类 含义
:hover 鼠标悬停在上面时
:focus 获得焦点时(输入框被点中、按钮被 Tab 选中)
:first-child 是父元素的第一个子元素时
:last-child 是父元素的最后一个子元素时
:nth-child(n) 是父元素的第 n 个子元素时(可以做斑马纹)
:not(选择器) 排除符合某条件的元素

伪元素(pseudo-element) :选中元素身上一个虚拟的"部分" ,双冒号 :: 开头。

伪元素 含义
::before 在元素内容之前 插入一块(需配 content)
::after 在元素内容之后 插入一块(需配 content)
::first-line 元素的第一行文字
::placeholder 输入框的占位提示文字
::marker 列表项前面的那个圆点/数字
css 复制代码
/* 鼠标悬停时变色(最常见的交互效果) */
button:hover { background: #2563eb; color: #fff; }

/* 输入框获得焦点时描边 */
input:focus { outline: 2px solid #2563eb; }

/* 列表斑马纹:奇数行加底色 */
li:nth-child(odd) { background: #f7f7f7; }

/* 排除最后一个元素(常用来去掉多余的间距) */
li:not(:last-child) { margin-bottom: 8px; }

/* 伪元素:在标题前加一个装饰小竖条 */
.title::before {
  content: "";            /* 必须有,哪怕空字符串 */
  display: inline-block;
  width: 4px; height: 16px;
  background: #2563eb;
  margin-right: 8px;
}

/* 伪元素:让段落第一行的字稍大一点(杂志排版常见) */
p::first-line { font-weight: bold; }

⚠️ 注意:伪元素不写 content 就完全不会出现。 ::before 和 ::after 必须至少有一条 content 声明(哪怕是 content: ""),否则浏览器认为"没有内容可插入",整个伪元素都不渲染 。这是新手调装饰性伪元素时最常卡住的地方------样式全写对了,就是看不见,因为漏了 content。
📌 记忆点:单冒号 vs 双冒号。

  • 伪类 :单冒号 :hover(因为它是"真的元素 + 一个状态",元素本身是存在的);
  • 伪元素 :双冒号 ::before(因为它选的是"虚拟出来的一块",本来不存在)。

也就是说:"冒号前有东西"的用单冒号,是伪类;"凭空造出一块"的用双冒号,是伪元素。 (历史上老的写法也用单冒号写伪元素,比如 :before,浏览器为兼容还认;但新代码请一律用双冒号。)

8.6 优先级:两条规则打架,谁说了算

现在到本章最关键的一节。

假设下面两条规则都指向同一个 <p>,字到底什么颜色?

css 复制代码
#intro { color: blue; }
p { color: red; }

答案是 蓝色 。为什么?因为 ID 选择器的优先级高于标签选择器。

浏览器判断优先级,用的是三列对比法 ------注意,它不是一个加法:

列 名称 里面装什么
A ID 列 有多少个 #id
B 类列 有多少个 .class、属性选择器、伪类
C 元素列 有多少个标签名、伪元素

比较规则:从 A 列开始,谁大谁赢。A 列相同才比 B 列,B 列相同才比 C 列。三列全一样,才看"源顺序"(后写的赢)。

⚠️ 纠偏:优先级不是 100 / 10 / 1 的十进制加法。 很多教程教你"ID 算 100 分、类算 10 分、标签算 1 分,加起来比大小"。这个算分法是错的 ------它会把 .a.b.c.d.e.f.g.h.i.j.k(11 个类)算成 110 分,得出"能打败 1 个 ID(100 分)"的结论。 真相是:三列是独立的,列与列之间不会"进位"。 11 个类在 B 列是 11,但在 A 列是 0;而 ID 在 A 列是 1。A 列一比,11 个类直接输。 请记住这句话:再多的类,也打不过一个 ID。

用例子把规则做实:

选择器 A(ID) B(类/属性/伪类) C(元素/伪元素) 谁赢
p 0 0 1 ---
.card 0 1 0 比 p 强(B 列先赢)
p.card 0 1 1 比 .card 强(C 列胜出)
#app p 1 0 1 比 p.card 强(A 列直接定胜负)
#app 1 0 0 比 #app p 弱(A 平,比 C)

几个权重为零的特例,一定要记牢:

选择器 权重 说明
*(通配) 0 不贡献任何权重
组合符 > + ~ 空格 不贡献 它们只是"关系",本身不加权重
:where(...) 0 整个东西权重为 0(8.8 节细说)

关于 !important 的忠告

!important 能无视上面所有规则,强行让一条声明获胜:

css 复制代码
p { color: red !important; }   /* 这条会盖过任何普通声明 */

它能救急,但代价很大:

  • 它破坏了"优先级"这套可预测的体系 ------别人看代码时,会以为某条规则生效,结果被一个 !important 悄悄改掉了;
  • 它会引发"军备竞赛" ------你用 !important 盖过一条,别人只好用更强的 !important 盖你,代码越来越乱;
  • 一旦满屏 !important,样式就彻底失控了。

✅ 正确的思路:遇到"改不动样式",先别掏 !important。 先按这个顺序排查:

  1. 我选中的真的是那个元素吗?(DevTools 里看看元素有没有被选中)
  2. 我的选择器权重是不是太低?(换个更高权重的,或调整选择器写法)
  3. 是不是有更靠后的同权重规则把它盖了?(调整源顺序)
  4. 真的都排查过了,才考虑 !important,并且写好注释说明原因。

8.7 层叠与继承:为什么有些样式会"自己传下去"

CSS 里有个神奇的现象:你只给 <body> 设了字体,为什么里面所有文字都跟着变了?

这叫继承(inheritance) ------某些属性会自动从父元素传给子元素。但不是所有属性都继承,这条界线要记清楚:

会继承的属性(典型) 不会继承的属性(典型)
color 文字颜色 border 边框
font-* 字体相关(字号、字重、字族) margin / padding 内外边距
line-height 行高 width / height 宽高
text-align 文本对齐 background 背景
letter-spacing 字间距 display 显示方式

规律其实很好记:

💡 想通一件事:继承的是"文字类的属性",不继承的是"盒子类的属性"。 文字相关(颜色、字体、行高)传给子孙很合理------一篇文章本来就该统一文字风格;而边框、边距、宽高不该 继承,否则给 <div> 加个边框,里面所有元素都长出边框,那就灾难了。

如果某个不继承的属性,你就是想让它继承 ,用关键字 inherit 强制:

css 复制代码
.child { border: inherit; }   /* 强行继承父元素的边框 */
.title { color: initial; }    /* 恢复成属性默认值(本来的初始值) */
.box   { color: unset; }      /* 该继承的就继承,不该继承的就用初始值 */

同时,别忘了"层叠"的另一半------源顺序。 当权重完全相同时,写在后面的赢:

css 复制代码
p { color: red; }
p { color: blue; }   /* 两条同权重,后者生效 → 文字是蓝色 */

⚠️ 一个真实的坑:<style> 和 <link> 的顺序。 前面说过,内部样式和外部样式权重一样 。所以如果你把 <style> 写在 <link> 之后 ,内部样式会盖掉外部样式;反过来,外部样式会盖掉内部样式。顺序不同,结果不同 ------这也是"我明明改了 CSS 文件,页面却没变"的一种常见原因(另一种更隐蔽:浏览器缓存了旧的 .css 文件,按 Ctrl+F5 强制刷新即可)。

8.8 现代选择器(入门彩蛋,认识即可)

CSS 这些年一直在进化。有几个新选择器已经在所有主流浏览器里稳定可用,虽然本章是入门,但值得你先混个脸熟------以后看到不会懵。

选择器 作用 例子
:is(A, B) 简写"或",继承参数里最高的权重 .post :is(h1, h2, h3) a { }
:where(A, B) 同 :is(),但权重恒为 0 :where(.prose) ul { }
:has(选择器) 父级选择器 :元素含有某内容时命中 .card:has(img) { }
css 复制代码
/* :is() ------ 少写一长串重复的选择器 */
/* 原来:.post h1 a, .post h2 a, .post h3 a { ... } */
.post :is(h1, h2, h3) a { color: inherit; }

/* :where() ------ 权重为 0,专用来写"随时可以被覆盖"的基础样式 */
:where(h1, h2, h3) { margin: 0; }   /* 谁都能轻松盖掉它 */

/* :has() ------ "父选择器",过去只能用 JS 实现的场景 */
.card:has(img) { display: grid; }        /* 卡片里有图片时,换个布局 */
.field:has(input:invalid) { border-color: red; }  /* 输入非法时,给整个字段描红 */

🔍 :has() 为什么被称为"游戏规则改变者"? 因为它让 CSS 第一次能够**"向上去看"**------根据"里面有没有某个东西",来决定"外面那个元素"的样式。过去这只能靠 JavaScript 监听事件、手动加减 class 类名来做。现在,一个选择器搞定。 它的支持已经覆盖了 Chrome、Edge、Safari、Firefox 的当前版本,可以放心用。

另外还有两个趋势值得知道(点到为止):

  • 原生 CSS 嵌套 :以前只有 Sass 这类预处理器才有"在父规则里写子规则"的能力,现在浏览器原生支持 了------.card { h2 { ... } &:hover { ... } } 直接就是有效的 CSS。
  • @layer(级联层) :给样式分层,层的顺序优先于选择器权重。它是大型项目治理"优先级战争"的终极手段------不过这是进阶话题,本书后面讲到工程化时再展开。

💡 入门阶段不必急着用这些。 先把基础选择器和优先级吃透------它们是理解一切新技术的底座。!important 和 @layer 解决的都是"优先级失控"的问题,而最好的优先级管理,是根本不让它失控。

8.9 动手:给新闻页穿上第一件衣服

现在把前面所有知识用起来。还记得第 7 章那个"结构规范但很素"的新闻页吗?我们给它加上样式。

第一步:建一个外部样式表(这是正式项目的做法,别再写行内样式了)。

目录结构:

text 复制代码
前端练习/
├── news.html
└── css/
    └── style.css

第二步:在 HTML 里引入样式表。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>科技日报 · AI 助手走进课堂</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <header class="site-header">
    <h1 class="logo">科技日报</h1>
    <nav class="site-nav">
      <a href="/">首页</a>
      <a href="/tech">科技</a>
      <a href="/edu">教育</a>
    </nav>
  </header>

  <main class="container">
    <article class="post">
      <h2 class="post-title">AI 助手走进课堂</h2>
      <p class="post-meta">2026-10-08 · 记者 张明</p>
      <p>近日,多所学校开始试点 AI 教学助手......</p>
      <p>据参与试点的教师反馈,学生对这种新形式的接受度很高。</p>
    </article>

    <aside class="sidebar">
      <h3>相关推荐</h3>
      <ul>
        <li><a href="/a">AI 如何改变备课方式</a></li>
        <li><a href="/b">教育数字化的三年</a></li>
      </ul>
    </aside>
  </main>

  <footer class="site-footer">© 2026 科技日报</footer>
</body>
</html>

第三步:写样式表。

css 复制代码
/* ===== 全局重置与基础 ===== */
* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: "Microsoft YaHei", system-ui, sans-serif;
  line-height: 1.8;
  color: #333;
  background: #f7f8fa;
}

a { color: #2563eb; text-decoration: none; }
a:hover { text-decoration: underline; }   /* 伪类:悬停时加下划线 */

/* ===== 页头 ===== */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 24px;
  background: #fff;
  border-bottom: 1px solid #e5e7eb;
}
.logo { margin: 0; font-size: 20px; }

.site-nav a {
  margin-left: 16px;
}
.site-nav a:not(:last-child)::after {   /* 伪元素 + 伪类 + 关系选择器 */
  content: "|";
  color: #ccc;
  margin-left: 16px;
}

/* ===== 内容区 ===== */
.container {
  max-width: 900px;
  margin: 24px auto;
  padding: 0 24px;
  display: grid;
  grid-template-columns: 2fr 1fr;   /* 主内容 + 侧栏 */
  gap: 24px;
}

.post {
  background: #fff;
  padding: 24px;
  border-radius: 8px;
}
.post-title { margin-top: 0; font-size: 28px; }
.post-meta { color: #888; font-size: 14px; }

/* 文章正文:紧跟标题的第一段去掉上边距 */
.post h2 + p { margin-top: 0; }

/* 侧栏 */
.sidebar {
  background: #fff;
  padding: 24px;
  border-radius: 8px;
}
.sidebar h3 { margin-top: 0; }
.sidebar ul { padding-left: 18px; }
.sidebar li:not(:last-child) { margin-bottom: 8px; }

/* ===== 页脚 ===== */
.site-footer {
  text-align: center;
  padding: 24px;
  color: #888;
  font-size: 14px;
}

第四步:打开看效果,并用 DevTools 验证。

把浏览器窗口缩放到不同宽度,再用 F12 检查:Elements → Styles 面板里,你能看到每一条样式是"从哪个选择器来的",以及哪条被划掉了 (被更高优先级的规则覆盖了)。学会看这个面板,你的 CSS 调试能力会直接翻倍。

💡 本节的样式表用到了什么? *(通配)· .class(类)· 标签 · :hover / :not() / :last-child(伪类)· ::after(伪元素)· 后代 / 子代 / 相邻兄弟(关系)· 还有下一章才细讲的 display: flex/grid。 这张表里的每一行,都是本章讲过的选择器。 你不是在抄代码------你是在验证自己真的会用了。

8.10 本章小结

  • CSS = 层叠样式表,负责"长什么样";一条规则 = 选择器 + 声明块,声明 = 属性 + 值。
  • 三种引入方式 :行内(style)· 内部(<style>)· 外部(<link href>)。正式项目一律用外部样式表。
  • 优先级真相 :外部与内部权重相同 ,同级看源顺序;行内样式在普通声明里最高,但能被样式表里的 !important 盖过。
  • 选择器 :基础(标签 / 类 / ID / 通配 / 属性)→ 关系(空格 / > / + / ~ / ,)→ 伪类(:hover 等状态)→ 伪元素(::before 等虚拟部分)。
  • 优先级不是十进制加法 ,是三列 (ID, 类, 元素) 从左到右比较------再多的类也打不过一个 ID。
  • 通配 * 和 :where() 权重为 0;组合符不贡献权重。
  • 继承的是"文字类"属性 (颜色、字体、行高),不继承的是"盒子类"属性(边距、边框、宽高)。
  • !important 是急救药------先排查选择器和权重,别急着用它。

8.11 练习题

第 1 题(辨析) 下面这个说法对吗?如果不对,错在哪?请改正。

text 复制代码
"外部样式表的优先级比内部样式表低,
 所以内部样式里的规则总能盖过外部样式里的规则。"

参考答案

说法错误。

错在哪 :内部样式表和外部样式表权重完全相同 ,浏览器把它们都当作"作者样式表"处理,没有天生的高低之分。

正确的规则 :当两条规则权重相同时 ,看源顺序 ------谁在文档里后出现,谁生效。

所以:

<head> 里的写法 结果
<style> 写在 <link> 之前 外部样式生效(它后出现)
<style> 写在 <link> 之后 内部样式生效(它后出现)

也就是说:决定胜负的不是"内部还是外部",而是"谁都位置更靠后"。

补充 :真正比它俩高一档的,是行内样式 (style="...")------它在普通声明里优先级最高。但注意:样式表里带 !important 的规则,仍然能盖过普通行内样式。

第 2 题(计算) 下面这些选择器,各自的三列权重分别是多少?把它们从低到高排序。

text 复制代码
①  p
②  .card
③  #app .card
④  article.post h2
⑤  * 
⑥  a:hover

参考答案

按 (A: ID, B: 类/属性/伪类, C: 元素/伪元素) 数:

选择器 A(ID) B(类/伪类) C(元素)
① p 0 0 1
② .card 0 1 0
③ #app .card 1 1 0
④ article.post h2 0 1 2
⑤ * 0 0 0
⑥ a:hover 0 1 1

从低到高排序 :⑤ *(全 0)< ① p(0,0,1)< ② .card(0,1,0)< ⑥ a:hover(0,1,1)< ④ article.post h2(0,1,2)< ③ #app .card(1,1,0)

几个要点:

  • ⑤ * 通配符权重为 0 ,但它确实会命中所有元素------"权重为 0"和"不生效"是两回事。
  • ② .card 和 ④ article.post h2 都是 B 列 = 1,但 ④ 的 A 列相同(都是 0)、比到 C 列(2 > 0)才输赢------说明 A 列没分出胜负时,才轮到后面比。
  • ③ 只要 A 列有 1 个 ID,B 列和 C 列无论多少都会被压住------这就是"现在的类打不过 ID"的含义。
  • ⑥ a:hover::hover 是伪类,算在 B 列,所以是 (0,1,1) 而不是 (0,0,2)。

第 3 题(纠错) 小美想让下面这段代码里所有段落的字变红,但一直没成功。请找出至少 4 处错误。

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    p { color = red }
    .text { font-size: 16 px; }
  </style>
</head>
<body>
  <p class="text">第一段
  <p class="text">第二段
</body>
</html>

参考答案

四处错误:

# 错误 正确写法 说明
① color = red color: red; CSS 的"属性"和"值"之间用冒号,不是等号
② 第一条声明结尾漏分号 color: red; 虽然它在大括号最后一行,分号仍建议写------否则一旦往下加声明就会连带出错
③ 16 px 16px 数值和单位之间不能有空格
④ <p> 标签没有闭合 <p class="text">第一段</p> HTML 的 <p> 虽然能"自动闭合",但这是坏习惯,容易造成结构错乱

逐条解释:

  • ① 和 ③ 属于"写错了但页面不报错" ------浏览器只会静默忽略 这一条声明,所以你会觉得"我明明写了却没效果"。这就是 8.1 节强调的:CSS 错误不弹窗,只能靠 DevTools 排查。
  • ④ 是 HTML 的问题 。虽然浏览器对 <p> 有"自动闭合"的容错,但别依赖它 。规范的写法是每段都写 </p>。

修复后的正确代码:

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <style>
    p { color: red; }
    .text { font-size: 16px; }
  </style>
</head>
<body>
  <p class="text">第一段</p>
  <p class="text">第二段</p>
</body>
</html>

第 4 题(实战) 按要求写出选择器(只写选择器,不用写声明块内容)。

html 复制代码
<ul class="list">
  <li>项目一</li>
  <li>项目二</li>
  <li>项目三</li>
</ul>
<p>段落一</p>
<p>段落二</p>
text 复制代码
① 选中所有 li
② 只选中"项目二"
③ 选中所有 li,但除了最后一个
④ 鼠标悬停在 li 上时
⑤ 选中 p 里除第一段以外的所有段落

参考答案

# 需求 选择器 说明
① 所有 li .list li 用后代选择器 限定在 .list 里,比单写 li 更精确
② 只要"项目二" .list li:nth-child(2) :nth-child(2) 选第 2 个子元素
③ li 除去最后一个 .list li:not(:last-child) :not() 排除 :last-child
④ li 悬停时 .list li:hover :hover 伪类
⑤ p 除第一段外 p:not(:first-child) 排除是"第一个子元素"的 p

几个要点:

  • ② 的 :nth-child(2) 是"第 2 个孩子" ,注意它数的是"在父元素里的名次",不是"第 2 个 li" 。如果 <ul> 里第一个元素不是 <li>,结果会不一样------这是 :nth-child 最容易误解的地方。
  • ③ 和 ⑤ 都用 :not() 。:not() 里可以放选择器,用来"排除"。⑤ 的 p:not(:first-child) 表示"p 元素里,不是第一个子元素的那些",也就是"段落二"。
  • ① 写成 .list li 而不是直接 li ------因为页面上可能还有别的列表,加一层 .list 能避免样式误伤(这就是"关系选择器"的价值)。

第 5 题(判断) 下面四段描述,判断对错并说明理由。

text 复制代码
① 通配选择器 * 权重为 0,所以它能被任何样式覆盖。
② 只要用了 !important,样式就一定能生效。
③ ::before 和 ::after 不写 content 属性也能显示。
④ 给 body 设置 font-size 后,里面所有文字都会继承这个字号。

参考答案

# 判断 理由
① ⚠️ 基本对,但表述要修正 * 权重确实是 0 ,同等条件下 会被任何有权的选择器盖过。但"能覆盖"不等于"一定被覆盖"------如果没有别的规则 指向该元素,* 的样式照样生效
② ⛔ 错 !important 只在作者样式表内部 争胜负。如果 CSS 写错了、选择器根本没命中元素、或者有语法错误,!important 一样救不了。它只解决"优先级打架",不解决"没选中"
③ ⛔ 错 ::before / ::after 必须有 content 属性 (哪怕 content: ""),否则完全不渲染。这是最常见的踩坑点
④ ✅ 对 font-size 属于"文字类属性",会继承 。所以在 body 上设一次,全站文字都会跟着改变------这正是"全局设字号"这个常用技巧的原理

深入说明:

  • ①的细节 :* 权重为 0,意味着任何 一个标签选择器(权重 0,0,1)都能盖过它。所以 * 通常只用来做"全局重置",比如 * { box-sizing: border-box; }------反正它权重最低,不会干扰别处。
  • ②的教训 :!important 是"优先级层面"的武器。如果问题出在更底层 (选择器没写对、元素根本不存在、文件没加载),它完全无能为力 。这就是本书反复强调"先排查、后 !important"的原因。
  • ④的后半句 :要注意继承的是字号 ,但如果子元素自己写了 font-size,就会覆盖继承值(继承的优先级最低------任何直接命中的规则都比"继承来的"优先)。

下一章预告:给页面"上色"。 有了选择器这把钥匙,下一步就是往里面填具体的样式了。第 9 章「颜色、文本与背景」会讲:颜色有哪几种写法(关键字 / HEX / RGB / HSL)各自适合什么场合、字体怎么设才不乱、文字的行高与对齐怎么做才舒服、背景色和背景图怎么配------让页面从"能看"变成"好看"的第一步。

说一声「继续」,我们就往下走。

相关推荐
猪猪拆迁队1 小时前
跨电脑复制共享-WebRTC 打洞踩坑
前端·后端·go
Amos_Web1 小时前
Rspack 源码解析(十八):Tree Shaking 如何用 SideEffects 重写模块连接
前端·rust·前端框架
百度一下吧1 小时前
前端包管理工具和使用手册
前端
anxiao_m2 小时前
水利数字孪生怎么选?主流可视化渲染平台深度横向测评
大数据·前端·人工智能·图形渲染·云渲染
kill5222 小时前
useRef
前端·javascript·react.js
蓝悦无人机2 小时前
从“摸形状“到“点到线面的距离“——聊聊 SLAM 前端(激光雷达篇)
前端·loam·数据关联·激光slam·点云特征提取·点到线面距离·icp/ndt
flash俊杰2 小时前
工程约束怎么不拖后腿
前端
霍营_托尼3 小时前
纯前端播 40GB 本地视频?我把浏览器改造成了「磁盘流式」播放器
前端·javascript·音视频开发
溪语流沙3 小时前
【Web全栈进阶】FastAPI工程化:APIRouter拆分 + 配置 + 依赖注入
java·前端·fastapi