本节摘要:当多条 CSS 规则同时改写一个元素的同一个属性,浏览器按"重要性、特异性、源码顺序"三步裁定最终赢家——这套机制就是 CSS 名字里"层叠"一词的由来。本节教你手算任意选择器的特异性、避开
!important的依赖陷阱、理解继承的边界,最终用开发者工具而不是猜测来调试样式冲突。
完成本节后,你将能够:计算任意选择器的特异性值;按层叠三步法判定冲突规则的胜负;解释 !important 何时该用、为何要少用;区分可继承与不可继承的属性并用 inherit/initial/unset 控制;在开发者工具中读懂被划掉的规则。
每个选择器都有一个"权重值",由四个数字组成 (a, b, c, d)。当两条规则冲突时,权重值大的那条生效。
特异性四档权重的比较流程
| 选择器类型 | 权重贡献 | 示例 |
|---|---|---|
| 内联样式 | 1,0,0,0 | style="color:red" |
| ID 选择器 | 0,1,0,0 | #header |
| 类、伪类、属性选择器 | 0,0,1,0 | .nav, :hover, [type] |
| 元素、伪元素 | 0,0,0,1 | p, div, ::before |
| 通用选择器 | 0,0,0,0 | *(不贡献权重) |
来看几个计算实例,每条都给出了计算过程:
/* p —— 1 个元素 → 0,0,0,1 */ p { color: black; } /* .highlight —— 1 个类 → 0,0,1,0 */ .highlight { color: blue; } /* #main p —— 1 个 ID + 1 个元素 → 0,1,0,1 */ #main p { color: green; } /* nav ul li a:hover —— 4 个元素 + 1 个伪类 → 0,0,1,4 */ nav ul li a:hover { color: red; } /* .card.featured —— 2 个类 → 0,0,2,0 */ .card.featured { border: 2px solid gold; } /* a[href^="http"]::after —— 1 元素 + 1 属性 + 1 伪元素 → 0,0,1,2 */ a[href^="http"]::after { color: #999; }
比较权重时从左到右逐位对比,高位分出胜负就不再看低位。0,1,0,0 大于 0,0,10,0——一个 ID 的权重高于任意数量的类选择器,哪怕你叠十个类也不行。这是初学者最常犯的认知错误:以为"人多势众"能压过 ID。正确的心智模型是"进制隔离":类数量再多也不会"进位"到 ID 那一档,就像个位数累到 99 也不会变成百位数一样。
当特异性相同时,后出现的规则胜出——"层叠"这个名字的由来。完整的层叠判定分三步,像法庭审理一样有严格的次序:
第一步:重要性。带 !important 的声明无条件胜出。但 !important 只在同一个属性上比较——一条规则里 color: red !important 不会影响 font-size 的竞争,两个属性各判各的。
第二步:特异性。不带 !important 的声明中,特异性高的胜出。
第三步:源码顺序。特异性相同时,后出现的胜出。在外部样式表中,后引入的文件覆盖先引入的文件;在同一个文件中,后面的规则覆盖前面的。
/* 规则 A —— 特异性 0,0,0,1 */ p { color: black; } /* 规则 B —— 特异性 0,0,1,0,高于 A,B 胜 */ .highlight { color: blue; } /* 规则 C —— 特异性 0,0,1,0,与 B 相同但出现在后面,C 胜 */ .highlight { color: red; } /* 最终效果:.highlight 的元素文字为红色 */
动手把这三条规则依次加进一个测试页面,每加一条刷新一次,亲眼看着颜色从黑到蓝再到红。三分钟的这个实验,比背十遍规则有效。
补充一个源码顺序的实战推论:<link> 引入的顺序就是优先级顺序。-reset 文件要在最前面,框架样式其次,你自己的样式放最后——写反了顺序,你的覆盖样式就会被框架压住,而且看起来"明明特异性更高啊"的疑惑会浪费你一下午。
!important 的危险!important 是 CSS 中的"终审改判"——它能让一条声明无视所有特异性计算,强制生效。
.text { color: blue !important; } #main .text { color: green; } /* 最终颜色:蓝色,因为 !important 无视了特异性差距 */
听起来省事,但在实际项目中应该尽量避免使用 !important。原因很简单:一旦你在某条规则上用了 !important,将来要覆盖它就只能用另一条 !important,然后特异性体系彻底失效,你的代码里到处都是感叹号,每条规则都在"喊",最后谁也听不清。
!important 少数合理的用途:一是工具类(如 .hidden { display: none !important; }),确保紧急工具不被任何组件样式覆盖;三是临时调试时快速验证"是不是被覆盖了"——验证完记得删掉;三是从第三方引入的旧样式表中救火。除此之外,正确的做法永远是调整特异性或源码顺序。
遇到"必须覆盖"的场景,与其加感叹号,不如提升自己选择器的特异性,而且是从右往左加:
/* 想覆盖 .card .title 的颜色 */ .card .title.title { color: #C62828; } /* 重复类名,特异性加 1,快而不脏 */
某些 CSS 属性会自动从父元素传递给子元素,这叫做"继承"。典型的继承属性包括字体、颜色、行高等文本相关属性。
body { font-family: "Microsoft YaHei", sans-serif; color: #333; line-height: 1.6; } /* 页面内所有元素都会继承这些样式,无需逐个设置 */
不是所有属性都能继承。盒模型相关属性(margin、padding、border、width、height)和布局属性(display、position、float)不会继承——这很合理,你不希望每个子元素都自动继承父元素的内边距和宽度。
判断规律:跟"文字"有关的多半可继承,跟"盒子"有关的多半不可继承。这条规律覆盖九成场景,拿不准时查一下属性文档即可。
可以用 inherit、initial 和 unset 关键词显式控制继承行为:
a { color: inherit; /* 强制继承父元素的颜色(放弃链接默认色) */ } .special { margin: initial; /* 重置为浏览器默认值 */ } .reset { all: unset; /* 一次性清除:继承属性恢复继承,非继承属性恢复初始值 */ }
| 关键词 | 行为 |
|---|---|
inherit |
强制继承父元素的值 |
initial |
重置为 CSS 规范定义的初始值 |
unset |
继承属性等同于 inherit,非继承属性等同于 initial |
revert |
回退到浏览器默认样式表的值 |
继承还引出一个高频面试题式的问题:继承来的值参与优先级竞争吗? 答案是不参与——任何直接命中元素的选择器(哪怕特异性 0,0,0,1 的元素选择器)都强过继承值。所以 body { color: #333 } 之上想用 p { color: #999 } 覆盖段落颜色,永远能成功。
构造一个包含全部冲突要素的案例。HTML:
<div id="content" class="wrap"> <p class="msg" style="color: purple;">我是什么颜色?</p> </div>
CSS 按顺序写四条:
/* 规则 1:0,0,0,1 */ p { color: gray; } /* 规则 2:0,1,0,1 */ #content p { color: green; } /* 规则 3:0,0,1,1 */ .wrap p { color: orange; } /* 规则 4:0,0,1,0 + !important */ .msg { color: red !important; }
逐步裁决:第四步先比重要性——规则 4 带 !important,其余不带,规则 4 直接获胜?慢着,还有内联样式 style="color: purple"。完整次序是:!important 声明(含内联加感叹号的)> 内联普通声明 > 特异性 > 源码顺序。所以最终答案是红色:规则 4 的 !important 压过了内联的紫色,也压过了特异性最高的规则 2。
继续实验:删掉规则 4 的 !important——现在内联紫色获胜;再删掉内联样式——特异性最高的规则 2(含 ID)绿色获胜,尽管规则 3 在源码上更靠后;最后删掉规则 2——规则 3 橙色压过规则 1,同因特异性。四轮删改,把整条裁决链从头到尾走了一遍。
再用开发者工具验证:选中该段落,Styles 面板里四条规则按胜负排列,输家的属性被划上删除线,颜色值变灰;Computed 面板则显示最终生效的那个值。以后遇到任何"样式不生效",第一反应就是打开这个面板看"谁被谁划掉了"。
把本节内容压缩成一张排错流程,贴在脑子里:
!important 压住了吗?(面板里会标注)六问过后,九成的"玄学样式问题"都有了着落。

图:特异性权重的四个层级与裁决次序
问:行内样式和 !important 打起来谁赢? 看两边谁带感叹号。两边都带时行内样式赢;只有样式表那边带感叹号时它反杀行内样式;都不带时行内样式赢。这也是为什么调试时"在元素面板里勾选行内样式的感叹号"是快速验证覆盖链的手段之一。
问:继承来的样式和元素选择器谁强? 元素选择器赢,而且是碾压式的赢——任何直接命中的选择器,哪怕特异性低到尘埃里,都胜过继承值。所以 body 上设的颜色挡不住任何一条直接给 p 设的颜色,哪怕那条规则写在更早的文件里。
问:媒体查询会提高优先级吗? 不会。媒体查询只是条件开关,里面的规则带着自己的特异性参与正常裁决。这带来一个经典陷阱:响应式覆盖样式必须与被覆盖规则同特异性或更高,且写在后面——否则断点内样式"写了没反应",检查方向是两条规则的特异性是否打平。
问:怎么优雅地覆盖第三方组件库的样式? 三步走:先看组件库有没有官方的主题变量接口,有则走正门;没有则用更高特异性选择器覆盖(重复类名或加父级);实在不行才考虑 !important 定点打击。覆盖时把理由写成注释,后来者会感谢你。
把三步裁决放回更完整的图景里,浏览器给相互冲突的样式排的完整座次大致是这样的:最底层是浏览器默认样式表(那些不加任何 CSS 时段落也有的字号与边距);其上是用户自定义样式(系统或辅助工具注入的偏好,比如放大字号);再往上才是作者的样式表——我们写的 CSS 一般都在这一层。作者样式内部再按"重要性、特异性、源码顺序"三步分出高下;而作者样式里带 !important 的声明甚至能压过用户样式的重要声明,这也是无障碍领域反复提醒"慎用感叹号"的原因——它可能盖掉用户为自己设置的阅读偏好。
理解这个分层还有一层实践价值:很多"样式怎么来的"的疑惑,答案是"来自浏览器默认样式表"。标题为什么天生加粗、列表为什么自带缩进、按钮为什么灰底圆角——都是默认表的功劳。全局重置的本质,就是有选择地推翻这层默认,让所有浏览器站在同一起跑线上;而 revert 关键字的作用则是把某个属性送回默认表的取值,介于"继承"与"重置"之间。
日常工作里你不需要背诵整张座次表,但要有"我的样式不是从零开始"的意识:页面的每一个默认外观都有出处,你写的每一条规则都是在参与一场已经开场的层叠博弈。这个视角能解释太多"看起来没写却长这样"的现象。
!important)> 特异性 > 源码顺序,内联样式介于两者之间。!important 克制使用:一个感叹号引来更多感叹号;覆盖优先考虑提特异性(重复类名)。选择器体系到此收官。下一章进入盒模型——浏览器计算元素尺寸和间距的底层逻辑,那是从"会写样式"到"能控制布局"的必经之路。