说句实话,做前端这几年,“CSS 选择器和优先级”这块我见过太多人卡壳了。写出的样式不生效、被覆盖、改一处动全站,很多时候不是不会写属性,而是没吃透选择器命中规则和权重计算。这篇我把自己平时踩过的坑和梳理过的知识点完整写出来,从基础选择器讲到优先级计算,再到实际调试手段,适合刚入门想系统搞懂 CSS 的朋友,也适合写了好一阵样式但总被“优先级”折磨的同行翻一翻。
1. 整体认知:为什么选择器与优先级是你必须过的坎
1.1 选择器到底是什么
我习惯把选择器理解成“CSS 里的寻址系统”。你在 HTML 里有一堆标签,浏览器的渲染引擎拿到 CSS 文件后,第一件事就是拿着选择器去匹配页面上的元素节点,匹配上了就给它套上对应的样式声明。这个过程类似于你在一堆快递里找属于你的那个包裹——选择器就是你的姓名和手机号后四位,命中得越精确,包裹越不可能拿错。
很多人写样式的习惯是“靠感觉”:反正我写个.box能生效就行,至于这条规则为什么覆盖了另一条规则,就完全靠试。这个状态在项目小的时候还勉强能撑住,一旦工程变复杂——第三方 UI 库、多端适配、团队协作——优先级冲突就会像地雷一样密集引爆。用好选择器、算清优先级,相当于给这个“寻址系统”建立了明确的规则,让每一行 CSS 的生效与否都可以被准确预言,而不是靠猜。
1.2 层叠、优先级、源代码顺序三者的关系
要理解优先级,先得把它放进 CSS 的“层叠”(Cascade)大框架里看。浏览器决定一个元素最终使用哪条样式规则,遵循三大判断维度的先后关系:
- 样式来源:浏览器默认样式、用户自定义样式、作者(就是我们写的)样式,来源越靠后越优先。
- 优先级(权重):同源情况下,选择器权重越高越优先。
- 顺序(就近原则):权重相同时,在样式表里越靠后写的规则越优先。
我可以明确告诉你,日常开发里 95% 的“为什么没生效”场景,根子都出在优先级或顺序上。后面我会专门用一节来讲怎么算权重,现在你先建立一个总框架:不是“写在后面就必定赢”,也不是“ class 就一定压过 id ”,一切都要落到具体权重值上。
1.3 学习路径建议:先会读,再会写,最后会查
我的建议是把这部分内容拆成三个阶梯来学,而不是一次性硬记所有选择器:
- 第一步是“读得懂”:看到一个选择器
.card .title:hover,你能准确说出来它匹配的是什么元素。 - 第二步是“写得对”:你想选中“某个容器下第一个子段落”,能快速想到用
.container > p:first-child。 - 第三步是“查得清”:样式没生效的时候,打开开发者工具,能立刻分辨是没匹配上、还是权重不够、还是被来源更高的规则覆盖了。
这篇博文的结构就是按这个逻辑展开的,你先有个学习地图,后面对号入座就行。
2. 基础选择器专项拆解与应用思路
2.1 类选择器(.className):日常开发的绝对主力
类选择器以点号开头,匹配的是 HTML 里class属性包含该值的元素,它的核心优势是“可复用、可组合”。一个元素可以同时有多个类名,比如<div class="card active">,.card负责基础卡片样式,.active负责高亮状态,两者互不干扰。这正是类选择器成为现代 CSS 基石的直接原因。
使用类选择器时,我更推荐你尽早建立“语义化类名”的意识。.red这种按描述外观命名的类名,一旦 UI 改版很快就会失效;而.text-danger或者.status-error表达的是“状态”和“语义”,换肤换色都不影响结构。往深了讲,这其实就是 BEM 等命名方法论想解决的事:块(block)、元素(element)、修饰符(modifier),比如.card__title--highlight,看完类名就知道这个元素是谁的子元素、处于什么状态,团队协作时几乎不需要额外沟通。
另外,类选择器一旦使用,默认就是“全局可命中”。同一个类名出现在十处,那十处全都会被改动。这是便利也是风险,后面讲到作用域隔离时我会再提。
2.2 ID 选择器(#idName):能不用就别用
ID 选择器以井号开头,匹配id属性。它的特殊之处有两个:一是每个页面中 ID 应当是唯一的,二是它的权重极高(这个问题的细节我会在“优先级计算”一节专门展开)。
很多初学朋友喜欢给特殊元素加 ID 然后写样式,比如#header { background: #333; }。这么写好写吗?好写。坏处在哪?当你想在另一个页面复用同样的头部样式时,ID 的唯一性反而成了枷锁,你只能去复制一份改 ID,或者强行让两个页面同时出现同一个 ID——这就已经违反 HTML 规范了。相比之下,.header类名随意复用,一点负担没有。
所以在实际项目里,我的默认原则是:样式永远用类选择器去挂,ID 只留给 JavaScript 做获取元素的钩子,或者用于页面锚点跳转。这样职责彻底分离,也不会出现“一个 ID 样式压得所有类样式都翻不了身”的尴尬局面。
2.3 标签选择器与通配选择器:适合重置,慎用于组件
标签选择器写法最简单,直接写p { margin: 0; },匹配所有该标签的元素。它的问题在于粒度太粗:你想给正文段落调字体,结果一不小心把卡片里的段落也改了。所以在现代 CSS 实践里,我会把标签选择器主要用在“重置样式”阶段,比如:
h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: normal; } ul, ol { margin: 0; padding: 0; list-style: none; }通配选择器*就更激进了,它匹配所有元素。* { box-sizing: border-box; }是我在几乎每个项目里都会写的一条全局规则,它把盒模型改成 border-box 之后,宽度计算会符合直觉,不用反复做减法。但你要谨慎给*加太多样式属性,比如* { margin: 0; }这类重置手段在遇到表单、组件库时会非常容易误伤,远不如用现代 reset 工具库(如 modern-normalize)来得省心。
2.4 类名粒度与“原子化 CSS”的启发
近两年“原子性 CSS”的概念很火,简单说就是把每一个样式属性拆成独立的类,比如.fw-bold { font-weight: bold; }、.text-center { text-align: center; },再用 Tailwind CSS 这类工具去生成。这种做法本质上改变了类选择器的使用粒度:从“语义化、高内聚”转向“功能单一、极致复用”。
我不打算在这里站队,但你必须意识到:类选择器的粒度设计决定了你后面维护时的幸福感。全站用 Tailwind 的项目,好处是几乎不用写自定义 CSS,坏处是 HTML 里一长串类名,阅读性直线下降;纯语义化的项目则相反,HTML 干净了,CSS 文件会越写越胖。如果你刚接触这块,可以两种思路都试试,再结合团队情况做选择。无论怎么选,类选择器始终是这一切的地基。
3. 复合选择器与进阶选择器实操
3.1 后代选择器与子选择器:一个空格和一个大于号的区别
最容易被忽略却又最常踩坑的,就是“后代选择器”和“子选择器”的区别。.container p匹配的是.container内部所有层级的p标签,不管是直接子级还是孙级;而.container > p只匹配.container直接子级的p标签。
这个差异在实际布局中差异极大。比方说你有这样的结构:
<div class="container"> <p>第一段</p> <div class="wrapper"> <p>第二段</p> </div> </div>.container p会把两段都染上颜色,而.container > p只染第一段。很多新手说“我明明用了后代选择器想把某个区域全包住,怎么连外面也变了”,往往是没意识到这个包裹规则会一路向下命中所有层级的标签。我的习惯是:能用子选择器明确表达层级关系时,尽量用子选择器,减少意外扩散。
顺带说一个重点实践建议:选择器“写得短”并不等于“写得快”。你写.container .wrapper .title这种超长链路,看着是命中准了,但每次浏览器匹配时都要沿 DOM 树向上检查。虽然现代浏览器对这类情况优化得很好,性能瓶颈在绝大多数业务场景里体现不出来,可它反映出你的类名结构不够健壮。更合理的做法是直接用.title配合一定的类名隔离(比如按组件加前缀),让选择器层级尽量扁平。
3.2 兄弟选择器:相邻与实际应用
兄弟选择器分两种:相邻兄弟选择器A + B,选中的是紧跟在 A 后面的第一个 B;通用兄弟选择器A ~ B,选中的是 A 后面所有同级 B。这俩结合 CSS 的伪类,能玩出很多不需要额外 JS 的交互效果。
我最常用的经典场景是“鼠标移入事件”配合显示。比如一个导航项的悬停状态要改变后面下划线的位置和宽度:
.nav-item:hover + .nav-line { width: 100%; }还有表单场景:输入框校验失败时,让后面紧跟的错误提示文案从隐藏变为显示,用input.invalid + .error-msg一行就能解决,比写 JS 去控制 class 更纯粹。这种“通过 DOM 结构关系和状态类名来驱动样式”的写法规避了状态管理成本,值得你养成习惯。
3.3 属性选择器:精准命中带特定属性的元素
属性选择器用方括号表示,能根据元素的属性或属性值来选择。基础写法有:
[type="text"]:精确匹配type属性等于 text。[href^="https"]:匹配 href 以 https 开头的链接,^表示“以……开头”。[href$=".pdf"]:匹配 href 以 .pdf 结尾的链接,$表示“以……结尾”。[title*="更多"]:匹配 title 属性中包含“更多”二字,*表示“包含”。
这一组选择器在不需要增加 class 的前提下,可以很精准地锁定元素类型。比如给站内所有外链添加一个小图标,完全不需要麻烦后端或内容编辑去额外加类:
a[href^="http"]:not([href*="mysite.com"]) { background: url("icon-external.png") no-repeat right center; padding-right: 18px; }这里还出现了一个很实用的组合::not()选择器。它接收一个参数,表示“除……之外”,上面的规则意思就是“所有以 http 开头的链接,但排除掉链接里包含自己域名的那些”,一行代码就把内链外链区分开了。实际开发中:not()调试起来很直观,建议你多用多练。
3.4 结构伪类与状态伪类:hover、focus、nth-child 的实战细节
伪类选择器以冒号开头,描述的是元素的状态或位置。常见的状态伪类有:
:hover——鼠标悬停,这就是热搜词里提到的“css 鼠标移入事件”的本体,它不是只有链接能用,任意元素都可以。:focus——元素获得焦点,输入框、按钮常用。:active——元素被激活(通常指鼠标按下瞬间)。:disabled、:checked——表单控件常用的状态。
结构伪类中,:nth-child()是最容易被搞混的一个。它接受一个公式参数,比如:nth-child(2n+1)会命中父元素下面的奇数位子元素。注意它计算的是“所有类型子元素中的位置”,不是“特定标签中的第几个”,所以当你写出.list li:nth-child(2)时,你得确认.list的第二个子元素确实是li,如果前面混了个div,这个选择器就匹配不上了。
另一个高价值伪类是:not(),它可以嵌套使用,也能和结构伪类结合。例如指定偶数行班次以外的所有行:
.table-row:not(:nth-child(2n)) { background: #fafafa; }这种写法比正向枚举“第1行、第3行、第5行”要优雅太多,最关键的是它不关心列表长度,动态数据增减都不影响规则成立。
3.5 伪元素:用 ::before 和 ::after 造出不需要 HTML 的“假元素”
伪元素用双冒号表示(实际开发中单冒号也能用,但规范上双冒号更严谨),它创建的并不是真实 DOM 节点,而是额外的样式层。::before和::after是最常用的两个,必须配content属性才会显示,哪怕 content 为空字符串也要写,否则不渲染。
这个能力非常适合做各种“辅助装饰”。举几个真实的例子:
- 段落前的删除线:不用在 HTML 里写额外的
<del>或<span>,直接用.price::after画一条贯穿线,再配合相对定位微调位置,视觉上就像实现了“css 删除线”。 - 优惠券的圆切齿:购物 App 里的优惠券卡片周围有半圆缺口,常见做法就是在左右两侧用
::before和::after画两个圆形遮罩,利用背景色相同实现视觉上的“咬合”效果,这就是热搜词里“css 优惠券圆切”的技术核心。 - 两行超出省略号:虽然更标准的方案是用
-webkit-line-clamp,但也有团队用伪元素配合渐变遮罩实现“底部淡出”的仿两行省略效果,兼顾视觉美感和溢出提示。 - 打点动画:通过
::after配合content在动画中切换内容,能实现“加载中……”的点点循环动画,省去额外 DOM。
对于伪元素布局,最需要小心的是它们默认都是行内元素,要想设置宽高和定位,必须显式加上display: block或position: absolute。我见过很多新手的伪元素“消失了”,排查到最后就是忘了这两点之一。
4. 优先级规则完整拆解与计算技巧
4.1 权重计算的底层逻辑:四位数计数法
优先级规则可以概括为一套非常机械的计算模型。业界最常用的方式是把选择器按“ID、类/属性/伪类、元素/伪元素”分成三级计数。我习惯用(a, b, c)三元组来表示:
a等于选择器中 ID 选择器的数量;b等于类选择器、属性选择器、伪类(比如:hover、:focus、:nth-child)的数量;c等于元素选择器、伪元素(如::before、::after)的数量。
比较优先级时,先从a开始比,a相同再比b,b相同再比c,逐级比较,绝不跨级。
举个例子,.container .title:hover,它的计数是(0, 2, 0),因为两个类选择器加一个伪类,伪类计入 b;再来一个div .title是(0, 1, 1),元素选择器计入 c。两相比较,(0, 2, 0)大于(0, 1, 1),所以前者优先。
还有个容易忽视的细节是,通配符*不计入权重,它相当于是 0 权重。而选择器列表h1, h2是拆开来逐条计算的,两边的权重可以各不相同,浏览器会分别给匹配元素套对应的规则。
4.2 常见的权重对照表
我把实用选择器按权重档位整理成一张浓缩速查表,你自己记的时候也按这个结构来:
| 选择器类型 | 示例 | 权重计数 |
|---|---|---|
| 内联样式 | style="color: red" | (1, 0, 0, 0),优先于所有选择器 |
| ID 选择器 | #header | (0, 1, 0, 0) |
| 类、属性、伪类 | .card,[type=text],:hover | (0, 0, 1, 0) |
| 元素、伪元素 | div,::before | (0, 0, 0, 1) |
| 组合器(空格、>、+、~) | .a > .b | 不额外加权,仅连接作用 |
通配符* | * | (0, 0, 0, 0) |
这里我用了四位表示法,把内联样式单列出来更直观。当你看到#nav .list li这种组合选择器时,只要逐个拆开计数再相加就行,它对应的是(0, 1, 1, 1)。
4.3 权重相同怎么办:看源代码顺序
当你写了两条权重完全相同的规则,比如:
.title { color: blue; } .title { color: red; }最终生效的必然是color: red,因为它在后面。这也解释了为什么很多人“把样式写到最后面就能生效”——这不是魔法,而是因为他们恰好通过源代码顺序打破了平局。
但在大型项目里,“写在最后”不一定可靠。如果你把一段样式写在一个非常靠前的公用样式文件里,后面模块的样式相同权重就能把你覆盖;反之亦然。所以,控制样式生效不能靠“最后一刻强行覆盖”,而是应当保证选择器权重语义清晰,并且约定样式文件的基本加载顺序(比如重置在前、组件样式在后、页面业务样式最后)。
另外,还有个容易被忽略的点:@media媒体查询内的规则不改变权重,它只是条件性地应用一组规则。一旦条件成立,里面的规则就走正常的权重比较流程,跟它写在哪个括号里毫无关系。
4.4!important的机制与克制用法
!important可以直接把一条声明的优先级拉满,甚至超过内联样式(当然两条都带!important时,继续按权重和顺序比较)。像是这样:
.title { color: red !important; }当你在项目里看到这条代码时,我建议你先别急着骂,因为某些场景确实不得不靠它。最典型的是覆盖第三方组件库的样式,你无法轻易修改库源码里那些深层嵌套的高权重规则,只能在自己的覆盖规则上加!important强行压制。
但问题也跟着来了:如果团队里人人手里都有“一票否决权”,规则就形同虚设了。你可能改一个样式时发现,旧的规则上加了个!important,你的新规则上也加了个!important,双方只能靠源代码先后顺序“碰运气”,代码脆得像纸片。所以我的建议是:
- 业务代码里优先通过提升选择器权重来覆盖,而不是挂
!important; - 只在覆盖组件库固有样式或处理极特殊场景时才使用;
- 使用后要在注释里写清楚原因,让后来者知道你为什么“掀桌子”。
4.5 继承机制:那些“没写也被应用”的样式
优先级讨论的大前提是“选择器命中了元素”,但 CSS 里还有一套完全不依赖选择器的机制:属性继承。像color、font-size、line-height这类文本属性会从父元素传给子元素,而margin、padding、border、width、height等盒模型属性则默认不继承。
继承机制解释了新手最常见的一个困惑:“我没给子元素设置 color,为什么子元素的文字颜色跟着父级变了?”答案就是它继承下来了。同理,你在某元素上用font-size: 20px,如果没有其他选择器干扰,所有子元素没显式设置字号时都会按 20px 去计算。
在需要刻意跳脱继承时,可以用几个内置关键词:
inherit:强制继承父元素计算值。initial:重置为浏览器默认值。unset:如果属性默认继承则表现为 inherit,否则表现为 initial。revert:回退到上一优先级来源的样式。
这几个关键词很少被提到,但用对场景很舒服。比如表单的input通常不继承 body 的字体,你想让所有表单控件都继承页面整体字体风格,直接写input, button, select, textarea { font: inherit; }一行解决。
5. 实际开发中的优先级冲突排查与调试技巧
5.1 打开开发者工具之后先看哪个面板
样式没生效时,第一步不是去改代码,而是按 F12 打开开发者工具,点中目标元素,右边“样式”面板会列出所有命中的规则,按优先级从高到低排序,被覆盖的规则会画上删除线。你要做的事是:
- 看目标元素到底有没有被选择器匹配到;
- 如果匹配到了,看它是输在权重低,还是输在顺序靠前;
- 如果压根没有匹配到,说明选择器写错了,去检查 HTML 结构和类名拼写。
这个流程我称之为“三层定位法”,能覆盖 90% 的“样式不生效”问题。比你在编辑器里抓瞎改半天要快得多。
需要注意,开发者工具里显示的“用户代理样式表”是浏览器默认样式,它属于来源最低的一层,我们写的任何选择器哪怕权重是(0,0,0,1)也能压住它。所以如果你看到目标属性上面只有灰色的 UA 样式,没有你自己的规则,那就要回头查选择器本身了。
5.2 权重失控的典型场景复盘
我在实战中见过的优先级失控,最典型的有这么几类,你对照排查大概率能命中:
- 组件库样式覆盖不生效:比如 Element Plus、Ant Design 这类组件库,内部很多规则都用了较深的嵌套选择器,你项目里写个
.card .button可能是(0,2,0),可组件库内部是.el-button--default.el-button加类名叠加,权重直接压过你,最终只能加!important或级联层次选择器。 - 响应式布局下的媒体查询覆盖:你写了
.box { width: 100px; },又在@media (min-width: 768px)里写了.box { width: 200px; },结果发现 768px 以上没变化。原因往往不是媒体查询没生效,而是你的媒体查询写在更早的样式文件里,或者选择器权重比不过另一条.page .box。 - 状态类与基础类抢样式:
.btn定义了背景色,.btn.active想改背景色,但因为你把.btn写在后面,导致.btn.active即使权重大一点却依然被覆盖(顺序相同时权重才比较?其实权重高的情况下顺序无关,真正的问题是你写反了权重)。这种细节最容易迷惑人,所以我每次都会亲自在浏览器里验证,而不是凭记忆判断。 - 多类名组合漏写:比如你要覆盖
.card下的标题,却写成了.title,结果所有卡片标题跟着变。解决方案一是加深嵌套增加权重,二是给目标卡片单独加一个业务类名。
5.3 降低优先级维护成本的工程化手段
与其天天和优先级搏斗,不如在工程层面做好几件事,让优先级冲突从根本上变少。
第一是统一选型规范。如果团队正在用组件库,就约定业务自定义样式尽量使用组件库提供的“custom class 插槽”,而不是暴力覆盖组件内部类;能写在组件根节点上的类,不要写进内部子元素。
第二是为组件作用域加前缀。老牌做法是.app-card这样的块级前缀,更现代的做法是 CSS Modules 或 Vue 的 scoped 样式。它们本质上都是“给类名加工哈希后缀”,让选择器天然隔离,互不干扰。这比你自己费心计算选择器权重省心太多。
第三是尽量避免写过于具体的长选择器。.sidebar .nav .item .link这种四层链路,权重高,维护难,一旦结构微调样式就断掉。我会优先考虑只用.sidebar-item-link这样的单类名,配合前缀保证唯一性。这样语义清晰,权重低,易覆盖,改动灵活。
5.4 排查示例:一个真实的三分钟定位过程
有次同事找我排查一个问题:某个按钮在移动端死活不显示红色背景,但代码里明明写了红色。我打开工具,先看样式面板,发现两条规则都在:
.btn-danger { background: red; } .page .btn-action { background: gray; }目标按钮同时有btn-danger和btn-action两个类,后一条规则权重是(0,2,0),前一条是(0,1,0),所以灰色背景赢了,红色被画上删除线。我并没有直接加!important,而是把.page .btn-danger提升成.page .btn-danger.btn-action,权重大幅提高,且语义表达“在这个页面里,同时具备两个状态的按钮,显示红色”。无论如何,整个过程没动一行组件库源码,也没引发其他样式连锁。
6. 高频问题速查与避坑建议
6.1 经典问题速查表
我把平时被问得最多的几个场景整理成速查表,你遇到类似情况可以直接对号入座:
| 问题现象 | 可能根因 | 排查与解决 |
|---|---|---|
| 我写了样式,但浏览器里看不到 | 选择器没匹配到,或权重被覆盖,或表达式被更早顺序的规则压制 | 开发者工具“样式”面板看是否命中、是否画删除线 |
| 同一个类名改了没反应 | 另一个更高权重规则同时命中目标 | 用第四维比较法或开发者工具分析命中列表 |
| 组件库样式改不动 | 组件库内部规则嵌套深、类名多,权重高 | 优先使用组件库提供的样式覆盖入口,必要时加!important |
| 媒体查询里的样式不生效 | 媒体查询条件不成立,或嵌套层级权重不足 | 先临时改条件为全屏查看是否生效,再查权重 |
| 伪元素没显示 | 忘记写content,或没设置display | 补content: "",设置display: block或position: absolute |
| 子元素不继承字体 | 标签自身有默认样式(如 input、button) | 使用font: inherit或color: inherit |
6.2 几个“没人提醒会一直踩”的经验教训
类名命名决定你有多少后悔药。我早期常写.left、.red这种描述性类名,后来改版一次全部重写。现在统一用语义类名,比如.is-error、.is-active,这类类名表达“状态”而不是“样子”,视觉改版时几乎不影响结构。这一点优先级再高也救不回来,Rename 成本极高。
不要试图用标签选择器解决组件内部样式。像p { margin: 0; }这种全局样式,一旦页面里接了富文本内容或第三方组件,会把它们的段落间距一并抹掉。如果确实要重置,尽量限定范围,比如.article-content p { margin: 0; }。范围限定会损失一点“简洁”,但换回来的是不误伤。
选择器不要一味追求短。很多人喜欢ul li这种极简写法,可一旦里面嵌套多层列表,所有子列表都会受影响。更稳妥的是.nav > li,或者干脆给列表项加独立类。权衡标准不是“打字少”,而是“将来别人能看懂、不误伤”。
计算优先级时,别把组合器数进去。组合器(空格、>、+、~)只是连接符号,不参与权重计算。你写的.wrapper .card > .title,计数就是两个类加一个类,总共(0, 3, 0),而不是(0, 4, 0)。
6.3 怎么练习更有效
理论看再多,不如自己动手验证几轮。这里给你两个练习思路:
- 刻意改写选择器:随便找一个页面组件,先写一个选择器实现效果,再用不用的选择器组合实现同一个效果。比方说为列表第一项添加特殊样式,技术上可以用
:first-child、:nth-child(1)、:not(:nth-child(n+2))等五种写法。这个过程能极大加深对选择器匹配方向的理解。 - 用交互式游戏:网上有专门的 CSS 选择器练习游戏,比如 CSS Diner,通过答题方式掌握不同选择器。它的场景很简单,但非常适合新手建立肌肉记忆,花一晚上刷完基础选择器和属性选择器,比死记硬背文档快乐得多。
另一个建议是,给自己设计一套小组件样式,特意去使用伪元素、属性选择器和兄弟选择器。比如实现一个 Tab 切换:默认第一个 Tab 高亮,鼠标移入其他 Tab 时切换高亮,完全可以在不用 JS 的情况下靠:hover和兄弟选择器完成。像这种主动练习,比看完一篇文章就划过要扎实太多。
我在实际排查优先级冲突时,最大的体会是:CSS 的表面语法很宽容,写错了也能运行,但只有当你建立了“选择器命中逻辑 + 权重计算体系”这套思维方式,写样式才真正从“调参碰运气”变成了“按图索骥”。尤其是占权重比最高的类选择器和 ID 选择器的取舍,直接决定你后面维护样式时是心情愉快还是焦头烂额。如果你现在再看某个“不生效”的样式,建议你先打开开发者工具,按我上面说的三层定位法走一遍,基本上十分钟内都能找到根因。CSS 这门语言入门门槛不高,但是要想写得干净、改得利索,优先级这座山早晚都得翻,越早翻过去,后面越舒服。