[ 面试系列 ] - 八:说一下 CSS 选择器优先级

2,592 阅读5分钟

系列文章

说在前面的

既然要聊 CSS 选择器的优先级,那自然要先搞清楚几个问题:

  • 什么是 CSS 选择器?
  • CSS 有哪些选择器?

什么是 CSS 选择器?

关于这个问题,我们先把 MDN 的说明贴上:

CSS 选择器规定了 CSS 规则会被应用到哪些元素上。

备注:暂时没有能够选择 父元素、父元素的同级元素,或 父元素的同级元素的子元素 的选择器或者组合器。

这说的是人话,浅显易懂,想必就不用上翻译咕咕咕了。

CSS 有哪些选择器?

至于 CSS 有哪些选择器,我们看下面的表就清楚了:

选择器 语法 例子
通用选择器 * -
类型选择器 节点名称 div
类选择器 . + 类名 .btn-primary
ID 选择器 # + ID 名 #form
属性选择器 [属性名] [self][data-type="hidden"]
伪类 :伪类名 :hover:focus
伪元素 ::伪元素名 ::before::after

事实上,CSS 还有组合器和组合选择器可以帮助我们更好的选择到目标元素,不过这和优先级关系不大,所以就不放在这里提了,有兴趣的同学可以看看工具书

CSS 选择器优先级

要说明这个问题,我们先来看下面这个问题:

div {
  background-color: red;
}

.div {
  background-color: yellow;
}

#div {
  background-color: blue;
}

想必大家都很清楚,最后 div 的颜色会是蓝色,那么为什么会出现这种情况呢?这就涉及到 CSS 选择器的优先级了。

关于 CSS 的优先级,大家通常第一时间会想到这个权重表:

权重 选择器
1000 内联
0100 ID 选择器
0010 类、属性、伪类选择器
0001 标签、伪元素

总的来说,就是:

内联 > id 选择器 > 类、属性、伪类选择器 > 标签元素、伪元素

而事实上,上面的表是存在一定问题的。

首先,以个十百千的位数计算,会给人一种误解:大于 10 个的类选择器优先级会难道比 1 个 ID 选择器的优先级更高吗?

对于这个问题,我们来看个例子就明白了:

<style>
* {
  margin0;
  padding0;
}
div {
  width100px;
  height100px;
}
.c1.c2.c3.c4.c5.c6.c7.c8.c9.c10.c11 {
  background-color: deeppink;
}
#i1 {
  background-color: deepskyblue;
}
</style>
<body>
  <div class="c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11" id="i1"></div>
</body>

结果如下:

img-01

显然,11 个类选择器优先级也并不比 1 个 ID 选择器的优先级更高,所以这种个十百千的计算方式是容易造成误解的。

想要搞清楚选择器优先级,可以参看 W3C 文章中对其的描述:

A selector’s specificity is calculated for a given element as follows:

  • count the number of ID selectors in the selector (= A)
  • count the number of class selectors, attributes selectors, and pseudo-classes in the selector (= B)
  • count the number of type selectors and pseudo-elements in the selector (= C)
  • ignore the universal selector

meme-01

别急,翻译咕这就来:

  • 首先,把权重分为 A、B、C 三个等级
  • ID 选择器为 A 级
  • 类、属性、伪类选择器为 B 级
  • 标签、伪元素为 C 级
  • 忽略通用选择器
  • A > B > C,每个等级单独计算优先级,只有相等的时候才会向后比较,如果全部都相等,则后定义的样式覆盖先定义的样式

上面的文档没有指出内联,当然,根据经验,我们可以把内联当做 S 级,即:

S > A > B > C

通常计算 CSS 属性的时候,我们通过上面的方式就可以确定了,但严格来说,这种说法依然不够严谨,它没有考虑 CSS 的另一个重要特性:层叠(比如 !important)。

(tip:其实从 CSS 的中文名——层叠样式表——都可以见得层叠对于 CSS 的重要性)

层叠

我们先来看看 MDN 对其的描述:

层叠是 CSS 的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在 CSS 处于核心地位,CSS 的全称层叠样式表正是强调了这一点。

层叠算法决定如何找出要应用到每个文档元素的每个属性上的值。

其算法核心有如下:

  • 首先过滤来自不同源的全部规则
  • 对规则进行排序

排序优先级如下:

优先级序号 来源
1 用户代理
2 用户
3 页面作者
4 CSS 动画
5 用户代理 !important
6 用户 !important
7 页面作者 !important
8 CSS 过渡

原文见:CSS Cascading and Inheritance Level 4

到这里又引出了两个新问题:

  • 用户代理是什么,用户是什么,页面作者又是什么?
  • 过度这么叼的咯?连 !important 都惹不起。

我们先来看第一个问题:

用户代理 浏览器的默认样式表
用户 浏览器用户自定义的样式表
页面作者 网页作者定义的样式表

那么按照上面的规则,在层叠方面,优先级应该如下:

CSS 过渡 > 页面作者 !important > 用户 !important > 用户代理 !important > CSS 动画 > 页面作者 > 用户 > 用户代理

.one,
.two {
  display: inline-block;
  margin-right100px;
  width100px;
  height100px;
  transition1s;
}
.one {
  background-color: deeppink !important;
}
.two {
  background-color: deeppink;
}
.one:hover,
.two:hover {
  background-color: deepskyblue;
}

结果如下:

gif-01

可见 transition 并没能影响到 !important,而在多平台实际测试下,发现最终优先级排序如下:

Chrome 80.0.3987.163、Safari 13.0.4、Edge 44.18362

CSS 动画 > 页面作者 !important > 用户 !important > 用户代理 !important > CSS 过渡 > 页面作者 > 用户 > 用户代理

参考