前言
相信每个前端开发尤其是初学者,在编写 CSS 的时候或多或少都遇到过类似问题:
- 给元素设置了一个样式属性,为什么没生效呢?
- 给元素设置了一个样式属性,生效了但是渲染出来和我写的不一样呢?
- 明明没给元素设置这个属性,怎么渲染出来还是有呢?
产生这些问题的根源都是因为对 CSS 属性值计算过程认识不够全面和清晰。
本篇文章将带着大家深入理解这个我认为每个前端都必须牢牢掌握,并且理解上不能有半点偏差的 CSS 概念。相信你学完之后从此写 CSS 再也不会胆战心惊担心出现意想不到的样式问题,就算出现了也有清晰的思路去排查。
什么是属性值的计算过程
打开浏览器开发者工具的 Elements 面板,选择一个元素,再选择右侧 Computed 窗格,可以看到应用到这个元素的最终 CSS 属性:

是不是发现很多属性你见都没见过,这么多也不可能是开发人员写样式的时候在代码里一个个手动声明的,那它们的值是哪来的呢?
这是代码里没有声明值的情况。那如果在代码里对同一个元素同一个属性声明了多个值,该如何决定最终应用哪个呢?
从 Computed 这个单词我们可以获取一些线索:计算的,表明所有这些属性值都是通过某种规则计算出来的。不管代码里有没有为这个属性声明值还是声明了多个值,经过计算最终都会有且只有一个值。
实际上,每一个 HTML 元素想在页面上成功渲染,它的每一个 CSS 属性都必须有且只能有一个值。这些属性从没有值到有的过程,就是 CSS 属性值的计算过程,其中涉及到了选择器优先级,层叠和继承机制,接下来一一讲解。
选择器特指度(优先级)
在我们编写样式时,同一个元素可能会被多个规则选择,并为同一个属性设置了不同的值,而且每个规则的选择器不尽相同,比如:
HTML
<div class="text">Hello World</div>
CSS
div {
color: red
}
.text {
color: blue
}
如果两个或多个属性声明有冲突,用户代理会计算每个规则中选择器的特指度,选择器的特指度最高的声明胜出。
选择器的特指度由选择器本身的组成部分决定。一个特指度值由四部分构成,例如0,0,0,0。选择器的特指度通过下述规则确定:
- 选择器中的每个ID属性值加 0,1,0,0
- 选择器中的每个类属性值、属性选择或伪类加 0, 0, 1, 0
- 选择器中的每个元素和伪元素加 0, 0, 0, 1
- 连接符和通用选择器不增加特指度
CSS
h1 {color: red} /* 0, 0, 0, 1 */
p em {color: purple} /* 0, 0, 0, 2 */
.grape {color: purple} /* 0, 0, 1, 0 */
*.bright {color: yellow} /* 0, 0, 1, 0 */
p.bright em.dark {color: maroon} /* 0, 0, 2, 2 */
#id216 {color: blue} /* 0, 1, 0, 0 */
div#sidebar *[href] {color: silver} /* 0, 1, 1, 1 */
刚刚举的例子中,第一个规则特指度是 0,0,0,1,而第二个是 0,0,1,0,因此最终 "Hello World"渲染出来会是蓝色。
有一点需要注意,特指度是从左往右比较,没有进位,比如 0,0,1,0 大于 0,0,0,10000。
目前见到的特指度都是以 0 开头,第一位就是为行内样式保留的,行内样式声明的特指度比其他任何都高。
这里有一张非常有意思的图,形象的展示了各种选择器的特指度:

总结一下就是:行内样式 > 类、属性和伪类 > 元素和伪元素,大致可以理解为选择范围越窄优先级越高,这样其实也很符合直觉。
层叠
CSS 的层叠机制(Cascading)是指浏览器如何解决多个样式规则作用在同一个元素时产生的冲突问题。
要问层叠机制在 CSS 中有多重要,CSS 全称是什么?Cascading Style Sheets 层叠样式表,名字里第一个词就是层叠,你说有多重要。
层叠机制核心目的是通过一套规则确定最终生效的样式,具体由以下四方面决定。
!important
有时某个声明可能非常重要,超过其他所有声明。CSS 称之为重要声明,需要在声明末尾的分号前插入!important。重要声明永远优于非重要声明。
CSS
p.dark {color: #333 !important; blackground: white}
样式来源
不同来源的样式优先级也不同:
- 重要声明:用户代理默认样式 > 读者提供的样式 > 开发者编写的样式
- 非重要声明:开发者编写的样式 > 读者提供的样式 > 用户代理默认样式
选择器优先级
如果两个或多个属性声明有冲突,选择器的特指度最高的胜出。
代码顺序
若来源和权重均相同,则后定义的样式覆盖先定义的样式("后来居上"原则)。
继承
继承指的是某些样式不仅应用到所指元素上,还应用到元素的后代上。
具体有哪些属性能继承可以在此查看:www.w3.org/TR/CSS2/pro...。
一般来说,像字体和颜色这样的样式属性是可以继承的,而像定位和盒模型属性这些布局属性都不能继承。
继承特指度
继承的值没有特指度,连0都没有,会被任意其他声明覆盖。
CSS
* {color: gray}
h1#page-title {color: black}
因为通用选择器应用于全部元素,且特指度为0,所以它声明的颜色 gray 击败继承的没有特指度的颜色 black。
这个例子充分体现了滥用通用选择器的潜在危险,会终结所有元素的继承。
强制继承
当一个声明的值设为 inherit,表示应用这个属性应该继承到的值
CSS
#toobar {color: white; blackground: black}
#toobar a:link {color: inherit}
完整计算过程
- 找到匹配特定元素的所有规则,包括开发者编写的样式、用户代理默认样式和读者提供的样式。
- 根据层叠机制排序:
- 按重要性排序,重要声明干掉所有非重要声明
- 相同的再按样式来源排序:
- 重要声明:用户代理默认样式 > 读者提供的样式 > 开发者编写的样式
- 非重要声明:开发者编写的样式 > 读者提供的样式 > 用户代理默认样式
- 还相同的计算选择器特指度排序
- 最后还有相同的按照声明前后排序,后面的覆盖前面的
- 对于三个样式表里都没有声明的属性,尝试从父元素继承
- 不可继承的属性,只能使用默认值了
结语
属性值的计算过程是 CSS 中非常核心的基础概念,它指的是一个 CSS 属性是如何从没有值到有值的过程。无论对于是日常开发还是求职面试都是至关重要的,掌握了它可以避免很多开发中可能出现的样式问题。
顺便说一句,这也是作者在 CSS 中最喜欢的内容之一哈哈哈。首先当然是因为它基础且重要,还因为真正理解之后会发现整个计算过程其实非常有逻辑而且符合直觉,完全不需要死记硬背。