嘛,说到写博客时的CSS样式,
果然还是会遇到一些规则“打架”的麻烦事吧。
明明写了正确的样式,
可页面元素还是我行我素,
最后只好掏出!important,
让代码的可读性变得一团糟了呢/(ㄒoㄒ)/~~
实则不然,
这一切的背后,
可是有**特异性(Specificity)**的这套规则在悄悄运作哦。
毕竟只有明白了这套规则,
才能写出优雅可控的CSS呢。
(笑
当然,我们先需要好好认识一下CSS选择器的种类。
先说说最基础的吧
1.CSS选择器种类
元素选择器
元素选择器是最常见的CSS选择器之一,它通过选择在一整个HTML文件中相匹配的标签,来赋予相匹配的样式。
语法形如
1 | 元素名称{ |
比如下面的示例↓
1 | h1{ |
这段示例将会把所有h1标签包裹的元素的颜色设置为黄色,文字大小设置为64px。
当然,这种方式一次给所有的同种标签来设置统一样式。可是嘛,用baka的脑子都会想到,当我们想给某些特定段落给特殊待遇的时候,这就有点相形见绌了。(´ー∀ー`)
类选择器
类选择器通过选择在整个HTML文件中,具有相同定义属性的元素,来赋予相匹配的样式。
语法形如
1 | .class属性名称{ |
使用语法格式形如:
1 | <标签名称 class="类选择器名称1 类选择器名称2....."> |
在类选择器中,你可以同时在一个标签中同时叠加两个以上的类选择器,其最终样式会叠加。当然,如果你使用两个以上的类选择器对同一个样式进行定义,那么最后定义的样式会起作用。
比如下面的实例↓
1 | <!DOCTYPE html> |
在这段实例中,我们对class1这个样式定义了两次,所以只有第二个会生效。而后我们又对h2标签使用了两个类选择器,最后会呈现叠加的效果,就像下面这样。ヾ(•ω•`)o

你可能会注意到,这种方式可能会比一股脑地全选好一些,当然地,不过如果有另一个指令也用类选择器来定义了冲突的样式话捏,那就要看他们谁在谁的后面。
ID选择器
ID选择器的作用效果与类选择器相似,但也有几点不同(°ー°〃):
ID选择器在引用时是使用ID属性
ID选择器的语法规定前面使用#号来引入
在同一个HTML文档中,只允许使用一次同一ID选择器(也就是说,不同的标签不可以拥有相同的ID,但可以拥有相同的class属性。这表明了ID选择器具有唯一性。)
ID选择器不能叠加使用
语法形如
1 | #ID名称{ |
使用语法格式形如:
1 | <标签名称 id="id名称"> |
比如下面的示例↓
1 | #exampleid1{ |
这段示例,将会把拥有exampleid1的标签所包裹的元素的颜色设置为黄色,文字大小设置为64px。
显然地,这是最精确的方式,一次只选择唯一的相匹配的标签。只因为如此,当给某个元素下指令时候捏,其他的选择器通常竞争不过它,除非有特殊情况。
后代选择器 父子选择器
我们先看一段代码片段:
1 | <div> |
后代选择器和父子选择器都是选择子元素的选择器。不同的是,后代选择器可以选择嵌套多层的元素,在上面的实例中,后代选择器可以直接选择到层级三(包含孙子和曾孙元素段落),而父子选择器只能选择到层级二(仅有孙子元素段落)。
后代选择器语法形如
1 | 祖先标签 后代标签{ |
父子选择器语法形如
1 | 父标签>子标签{ |
参照下面的示例,你可以更好体会它。Tips:在这段实例中,后代选择器和父子选择器权重相同。
1 | <!DOCTYPE html> |
在这段实例中,由于后代选择器可以可以选择嵌套多层的元素,所以第一个hello world的world(层级二)和第二个hello world的world(层级三)均可以被添加为为蓝色以及62px。而又因为父子选择器只能选择到层级二,所以只有第一个hello world的world(层级二)被添加underline。

这种选择器它通过描述谁里面的谁来精准定位目标。而且,它的优先性由它的几个部分叠加所成。(⊙o⊙)
其他
伪类选择器:适用于选择处于特殊状态的元素,不是元素的固定属性,而是元素的临时状态。
语法形如:
选择器 伪类{
样式属性:属性值;
样式属性:属性值;
……
}分组选择器:如果有些选择器定义的样式完全相同,那么就可以使用分组选择器来定义相同的样式。
语法形如:
标签1,标签2……{
样式属性:属性值;
样式属性:属性值;
……
}相邻选择器:如果两个元素拥有一个相同的父元素,那么可以使用相邻选择器。
语法形如:
子标签1+子标签2{
样式属性:属性值;
样式属性:属性值;
……
}属性选择器:可以选择有指定属性的元素进行样式设置,比如超链接。
语法形如:
标签名称[属性]{
样式属性:属性值;
样式属性:属性值;
……
}
那么,一个很自然的问题就来了……
现在你知道了这些种类繁多的CSS选择器。
但……
1 | <!DOCTYPE html> |
在这段实例中,“Hello,World!”的颜色究竟是红色还是蓝色?╰(‵□′)╯
浏览器到底应该听谁的?
事实上,早在1996时的CSS1的发布上,W3C就规定了一套规则,用来解决上面这段实例中的问题……
2.CSS特异性(Specificity)
基本定义与规则
CSS特异性(Specificity)是指浏览器**在解析 CSS 时,决定哪条样式规则将最终应用于元素的优先级计算系统。**或者说,可以把特异性想象成选择器的”权重分数”——分数高的选择器说了算q(≧▽≦q)!
一般地,我们用四级权重系统来表示特异性,格式为:(a, b, c, d)。对于权重系统,有以下规定:
| 等级 | 名称 | 表示方式 | 权重值 | 示例 |
|---|---|---|---|---|
| A | 行内样式 | (1,0,0,0) | 1000 | style=”color: red;” |
| B | ID 选择器 | (0,1,0,0) | 0100 | #header |
| C | 类/属性/伪类 | (0,0,1,0) | 0010 | .class, :hover, [type] |
| D | 元素/伪元素 | (0,0,0,1) | 0001 | div, p, ::before |
| E | 通配符 | (0,0,0,0) | 0000 | * |
当然,我们可以有更形象的比喻↓

来自《跟Wakaba酱一起学网站制作》书籍
针对于权重系统,有以下比较规则:
先比较a:行内样式数量多的胜出
如果a相同,比较b:ID选择器数量多的胜出
如果b相同,比较c:类/属性/伪类选择器数量多的胜出
如果c相同,比较d:元素/伪元素选择器数量多的胜出
如果都相同:后出现的规则胜出(后来居上原则)
让我们把之前提到的几个选择器来比较一下权重:
p → 0个ID,0个类,1个元素 → (0,0,0,1)
.highlight → 0个ID,1个类,0个元素 → (0,0,1,0)
#main → 1个ID,0个类,0个元素 → (0,1,0,0)
如果它们三个都想给同一个段落上色,显而易见地:#main 的 (0,1,0,0) 胜出,因为它的b列(ID列)最大。
让我们再思考一个问题:
1 | /* styleA */ |
在这段示例中,如果这个标签:<div class="class1">中所包裹的元素同时被styleA和styleB来选中,那么它的颜色会是什么?(请先自己思考)o_o ….
我们可以简单计算一下:根据特异性规则可得:
styleA:0个ID,0个类,11个元素 → (0,0,0,11)
styleB:0个ID,1个类,0个元素 → (0,0,1,0)
根据比较规则,显而易见,a b列都是0,持平。比c列,styleA是0,styleB是1,(0,0,1,0)胜出!
所以最终颜色是蓝色。
这个问题揭示了特异性的一个核心:容易看出来,一个“类“的权重,永远高于无数个“元素”的叠加。由此可以容易看出来,权重比较比的不是数量,而是等级。(和正常人类计算规则的“进制”不同)ヾ(^▽^*)))
不过,当选择器组合使用时,它的特异性是各部分相加,所以在遇到这种情况时,我们需要准确地分辨出各个选择器的种类,并把它们相加,同时,这也是最容易让人迷惑的地方(((φ(◎ロ◎;)φ))),我们多看几个例子。
1 | /* styleC */ |
在这段示例中,styleC和styleD哪个权重更大一些呢(._. )>?
容易计算到:
styleC:1个ID (#nav),1个类 (.list),2个元素 (li, a) → (0,1,1,2)
styleD:1个ID (#nav),1个类 (.list),4个元素 (div, ul, li, a) → (0,1,1,4)
根据比较规则,显然,在d中,4>2。
所以styleD的权重要大一些。
特殊规则
CSS在不断进化,新的功能性伪类带来了更灵活的写法,也带来了特异性计算的新规则。 o(* ̄▽ ̄*)o
通配符
通配符选择器 *权重为 (0,0,0,0),不影响特异性计算。
比如说:
1 | *{ |
组合符
组合符(>, +, ~, 空格)本身不增加权重。
比如说:
1 | h2 span{ |
:not() 伪类
:not() 伪类本身不计权重,但其括号内的选择器正常计算
比如说:
1 | :not(.disabled) { |
:is()伪类
:is():取参数列表中特异性最高的那个。
比如说
1 | :is(#nav, .main, div)p{ color: red;} |
:where()伪类
:where():“权重归零器”!(o゚v゚)ノ无论括号里写什么,整个 :where() 部分的特异性为 (0,0,0,0)。
比如说:
1 | :where(#nav, .main p{ color: blue;} |
我们再来看一个例子:
1 | /* styleE */ |
此时title会显示什么颜色呢?
根据特异性规则,可以得到:
styleE::where()权重为0,加上 .title 的 (0,0,1,0) → (0,0,1,0)
styleF:1个元素 (h1),1个类 (.title) → (0,0,1,1)
根据比较规则,显而易见,a b都是0,c均为1,持平。比d,styleE是0,styleF是1。(0,0,1,1) 胜出!
所以说,此时title会显示黑色。
特异性完全相同时的特殊类型
如果特异性完全一样呢?浏览器会遵循层叠的另一个核心原则:顺序靠后的胜出。(‾◡◝)
比如说:
1 | .class1{ |
!important
!important 声明会打破特异性体系。它会让一条样式声明获得最高优先级,但滥用会导致特异性体系崩溃,让代码变成一坨史山。只有在用尽其他方法都无法解决时,才考虑 !important,并且一定要加上注释说明原因。否则的话,只要过了一宿你就会不知道自己究竟写了什么东西。>﹏<
而且,若多个 !important冲突,则会回归特异性比较。
1 | p{ |
一般的前端开发中对于选择器的使用原则
当然,如上文所说,在一般的的前端开发中,不会遇到这些乱七八糟的选择器粘在一起让你算权重的情况,毕竟这就像你去711买个饭团,结果非要掏出计算器用微积分算它的热量分布一样,属于是把简单问题复杂化的行为呢 。
但有几条约定俗成的部分需要我们了解一下。
BEM命名法
BEM命名法核心思想就是:只用类选择器,通过命名规范来模拟层级关系,而不是用CSS的后代选择器来创建层级。
我们先来看一段实例
1 | <div class="card"> |
如果用常规思维,我们可能会写:
1 | .card .title { font-size: 20px; } |
但BEM的做法是,把所有样式都绑定在单个类名上:
1 | .card__title { font-size: 20px; } .card__btn { color: blue; } |
显然我们能看出来,这样几乎永远不需要用 ID,也永远不需要超过两层的嵌套。整个项目的特异性都一样的的,不存在谁意外超过谁的问题。
而且,类名本身即文档——看到 .card__title 就知道它是card块里的 title元素,不需要翻HTML结构去理解选择器含义。q(≧▽≦q)
单一类选择器优先原则
尽量或者只能用类选择器,禁止用ID和标签选择器来定义核心样式。
ID 因为其极高的特异性 ,一旦在CSS中使用,后续任何覆盖都必须用更高的特异性或!important。因此ID主要留给JS使用,不应该出现在样式表里。
标签选择器 ,它们会影响所有同标签元素。通常在样式重置或基础排版层才会少量使用(例如设定全局字体、基础行高),组件级别的样式一律不用裸标签选择器。
不要过度嵌套选择器
这样会变得非常难以理解,特异性不但会随着每一层嵌套叠加,而且会影响性能`(*>﹏<*)′
善用现代选择器
现代CSS提供了很多工具,让我们可以用更低特异性、更简洁的方式实现过去需要嵌套或叠加选择器才能做到的事,如上文提到过 :where() :is() :not()等。
尾……
嘛,说到底,
CSS的世界也没有那么难懂吧。(´▽`)
所以呢,下次再遇到样式冲突,
别急着掏用!important。
先静下心,打开开发者工具
像解谜一样看看那些 (a, b, c, d) 的数字
你会发现,
CSS其实还是很讲道理的吧。
学会了这些,
才是真正能掌控了样式的技巧呢。
(笑