欢迎在 freeCodeCamp 社区阅读原文

前言

直接说结论,CSS 选择器(selector)的权值(以下直接称为“CSS 权值”)不应以进制来理解,因此进制既不是 10,100,也不是 256。
笔者看到过很多文章,发现有很大一部分人认为 CSS 权值是 256 进制甚至是 10 进制的。
下文首先解释 CSS 权值应该如何正确理解,然后尝试解释为何有人会理解为 256 进制。
本文代码运行环境:

  • Google Chrome 版本 80.0.3987.149(正式版本)
  • Firefox 74.0 (64 位)
  • Opera 版本:67.0.3575.97
  • Safari 版本13.0.2 (15608.2.30.1.1)
  • 抱歉无法在 Edge 和 IE 上测试

1、何为 CSS 权值?如何正确理解与比较?

1.1 定义

Specificity is the means by which browsers decide which CSS property values are the most relevant to an element and, therefore, will be applied. Specificity is based on the matching rules which are composed of different sorts of CSS selectors. (MDN web Docs)

If there are two or more conflicting CSS rules that point to the same element, the browser follows some rules to determine which one is most specific and therefore wins out. Think of specificity as a score/rank that determines which style declarations are ultimately applied to an element. (w3schools)

在英文文档中,用 Specificity 这个单词来描述所谓的 CSS 权值。

Specificity 是“特异性”的意思,CSS Specificity 是应用于 CSS 选择器的规则集,以确定哪些样式应用于元素。简单地说就是特异性决定了哪些样式起作用。那么应该如何理解以及比较呢?

1.2 理解及比较

Every selector has its place in the specificity hierarchy. There are four categories which define the specificity level of a selector.

每个选择器在特异性层次结构中都有其位置。 有四个类别定义了选择器的特异性级别。
那么我们可以理解为 A, B, C, D 四个类别,初始各个位置都是 0,即 0,0,0,0。
有一张图描述得很好:

四个类别:

  • A: 内联样式,写在 HTML 标签中的 style=””,属于 A 类。若有,那么 A 位置加一,可以理解为 1,0,0,0。
  • B: id 选择器(如 #id1),如 id=””,属于 B 类,若有,那么 B 位置加一。即 0,1,0,0。
  • C: 类选择器(如 .class1)、伪类(如 :hover)、属性(如 [ type=”radio” ])为 C 类。有则 C 位置加一,即 0,0,1,0。
  • D: 标签(如 div),伪元素为 D 类,若有则 D 位置加1,即 0,0,0,1。

    规律与注意事项

Universal selector (*), combinators (+, >, ~, , ||) and negation pseudo-class (:not()) have no effect on specificity. (The selectors declared inside :not() do, however.)

  • 通配符选择器 (*),组合选择器 (+, >, ~, ‘’, ||) 和否定伪类 (: not ( )) 对特异性没有影响。但是在:not( )内部声明的选择器可以。

When an important rule is used on a style declaration, this declaration overrides any other declarations.

  • !important 是一种特殊的声明,它的级别高于其他所有的普通声明。这个链接里有一些关于 !important 的使用注意事项,中文资料也很多,不再赘述。

  • 比较时,A, B, C, D 四位依次比较,A 较大的权重大,不用看后面。当 A 相同时,比较 B。以此类推。

  • 当 A, B, C, D 相同时,后面的规则会覆盖前面的规则。

A class selector beats any number of element selectors

  • 一个类选择器可以击败任意数目的元素选择器。这就说明进制的概念在理论上是不应存在的。即 0, 0, 1, 0 应大于 0, 0, 0, D。 D 可以取任意值。因此比较权重时,还是应该按位去比。

这个链接里也有一张有意思的图,方便大家理解。

1.3 举例说明

1
2
3
4
5
6
7
8
9
body div {/*此处权重 = 0,0,0,2*/
background-color: red;
}
div {/*此处权重 = 0,0,0,1*/
height: 100px;
width: 100px;
background-color: green;
}
<div>Test CSS Specificity</div>

1
2
3
4
5
6
7
8
9
10
11
12
div {
width: 100px;
height: 100px;
}
.testClass.testClass {/*权重为0,0,2,0*/
background-color: yellow;
}
.testClass {/*权重为0,0,1,0*/
background-color: black;
}

<div class="testClass">Test CSS Specificity</div>

这里显示为黄色,说明重复的类选择器权重也会累计。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
div{
width: 100px;
height: 100px;
}
.testClass.testClass {/*权重为0,0,2,0*/
background-color: yellow;
}
.testClass {/*权重为0,0,1,0*/
background-color: black;
}
#testClass {/*权重为0,1,0,0*/
background-color: blue;
}
<div class="testClass" id="testClass">Test CSS Specificity</div>

各种各样的例子网上很多,不再赘述。下面第二部分举一些反例证明进制的思想是不合适的。

2、为何进制不是10,100或256呢?

2.1 反例

十一个类选择器,与一个 id 选择器。显而易见的,进制并不是 10。最后还是 id 选择器奏效。

1
2
3
4
5
6
7
8
9
10
11
div{
width: 100px;
height: 100px;
}
#testId {/*权重为0,1,0,0*/
background-color: purple;
} .testClass1.testClass2.testClass3.testClass4.testClass5.testClass6.testClass7.testClass8.testClass9.testClass10.testClass11 {/*权重为0,0,11,0,并没有任何作用*/
background-color: red;
}

<div class="testClass1 testClass2 testClass3 testClass4 testClass5 testClass6 testClass7 testClass8 testClass9 testClass10 testClass11" id="testId">Test CSS Specificity</div>

256个类选择器能干掉一个 id 选择器吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#id {/*权重为0,1,0,0*/
background: red;
}

.c000.c001.c002.c003.c004.c005.c006.c007.c008.c009.c010.c011.c012.c013.c014.c015.c016.c017.c018.c019.c020.c021.c022.c023.c024.c025.c026.c027.c028.c029.c030.c031.c032.c033.c034.c035.c036.c037.c038.c039.c040.c041.c042.c043.c044.c045.c046.c047.c048.c049.c050.c051.c052.c053.c054.c055.c056.c057.c058.c059.c060.c061.c062.c063.c064.c065.c066.c067.c068.c069.c070.c071.c072.c073.c074.c075.c076.c077.c078.c079.c080.c081.c082.c083.c084.c085.c086.c087.c088.c089.c090.c091.c092.c093.c094.c095.c096.c097.c098.c099.c100.c101.c102.c103.c104.c105.c106.c107.c108.c109.c110.c111.c112.c113.c114.c115.c116.c117.c118.c119.c120.c121.c122.c123.c124.c125.c126.c127.c128.c129.c130.c131.c132.c133.c134.c135.c136.c137.c138.c139.c140.c141.c142.c143.c144.c145.c146.c147.c148.c149.c150.c151.c152.c153.c154.c155.c156.c157.c158.c159.c160.c161.c162.c163.c164.c165.c166.c167.c168.c169.c170.c171.c172.c173.c174.c175.c176.c177.c178.c179.c180.c181.c182.c183.c184.c185.c186.c187.c188.c189.c190.c191.c192.c193.c194.c195.c196.c197.c198.c199.c200.c201.c202.c203.c204.c205.c206.c207.c208.c209.c210.c211.c212.c213.c214.c215.c216.c217.c218.c219.c220.c221.c222.c223.c224.c225.c226.c227.c228.c229.c230.c231.c232.c233.c234.c235.c236.c237.c238.c239.c240.c241.c242.c243.c244.c245.c246.c247.c248.c249.c250.c251.c252.c253.c254.c255 {/*权重为0,0,256,0,并没有任何作用*/
background: blue;
}

test {
display: block;
height: 100px;
width: 100px
}
<test id="id" class="c000 c001 c002 c003 c004 c005 c006 c007 c008 c009 c010 c011 c012 c013 c014 c015 c016 c017 c018 c019 c020 c021 c022 c023 c024 c025 c026 c027 c028 c029 c030 c031 c032 c033 c034 c035 c036 c037 c038 c039 c040 c041 c042 c043 c044 c045 c046 c047 c048 c049 c050 c051 c052 c053 c054 c055 c056 c057 c058 c059 c060 c061 c062 c063 c064 c065 c066 c067 c068 c069 c070 c071 c072 c073 c074 c075 c076 c077 c078 c079 c080 c081 c082 c083 c084 c085 c086 c087 c088 c089 c090 c091 c092 c093 c094 c095 c096 c097 c098 c099 c100 c101 c102 c103 c104 c105 c106 c107 c108 c109 c110 c111 c112 c113 c114 c115 c116 c117 c118 c119 c120 c121 c122 c123 c124 c125 c126 c127 c128 c129 c130 c131 c132 c133 c134 c135 c136 c137 c138 c139 c140 c141 c142 c143 c144 c145 c146 c147 c148 c149 c150 c151 c152 c153 c154 c155 c156 c157 c158 c159 c160 c161 c162 c163 c164 c165 c166 c167 c168 c169 c170 c171 c172 c173 c174 c175 c176 c177 c178 c179 c180 c181 c182 c183 c184 c185 c186 c187 c188 c189 c190 c191 c192 c193 c194 c195 c196 c197 c198 c199 c200 c201 c202 c203 c204 c205 c206 c207 c208 c209 c210 c211 c212 c213 c214 c215 c216 c217 c218 c219 c220 c221 c222 c223 c224 c225 c226 c227 c228 c229 c230 c231 c232 c233 c234 c235 c236 c237 c238 c239 c240 c241 c242 c243 c244 c245 c246 c247 c248 c249 c250 c251 c252 c253 c254 c255">test 256</test>

这个 256 的测试代码修改自张鑫旭的一篇博客
结果显而易见,256 个 class 并不能“击败”一个 id,所以进制不是 256。
100 就不再试了。所以进制的说法,从实际看来,也是不合适的。

BUT!你可以发现,在张鑫旭的这篇博客中(2012 年 08 月 20 日),256 个类选择器确实干掉了一个 id 选择器!那也就是说,进制的说法至少在当时是有据可依的。那具体是为什么呢?结合我搜索到的资料,简单的“猜想”一下。

2.2 为什么会有进制的说法?

通过张鑫旭博客,我们可以看到,在当时,确实 256 个类选择器干掉了一个 id 选择器。

这篇博客中,他提到了:

据说,查看FireFox浏览器的源代码,发现,所有的类名(classes)都是以8c比特数据类型存储的。对比特稍稍了解的人都知道,8比特所能hold的最大值就是255. 所以你想啊,当同时出现256个class, 势必会越过其边缘,溢出到id区域。

Gecko overflows the count of classes into the count of IDs, each of which holds 8 bits.

— Cameron McCormack (@heycam) August 16, 2012

根据一个Opera员工的信息,Opera浏览器class类名是以16比特数据类型存储的。因此,该浏览器要想发生class溢出到id的话,需要连续65536个class. 也不知道是不是因为16比特字符串比8自己的更影响选择器引擎

yes, opera uses 16 bits instead of 8. bring on 65536 classes…

— patrick h. lauke (@patrick_h_lauke) August 16, 2012

也就是说,虽然标准是那么规定的,但是落实到浏览器上,实际效果确是不尽相同。
因此我个人认为,这种不存在进制的,按级别按位比较的方式,应该更合理一些。


写在最后:
以上是我个人的理解,欢迎批评指正。
大家如对 CSS 的其他话题感兴趣,也欢迎通过我的邮箱与我交流学习。