CSS 把「长什么样」从 HTML 里剥出来。 它靠选择器命中元素,靠特异性决定谁赢——算成一个 (a, b, c) 三元组,逐位比大小。 平手才看谁写在后面。
上一节讲结构,这一节讲表现。看懂优先级,样式冲突就不再是玄学。
一、把长相单独放一处
一条 CSS 规则长这样:选择器 { 属性: 值 }。选择器说「管哪些元素」,花括号里说「改成什么样」。
这些规则可以写在 HTML 的 <style> 里,也可以单独放一个 .css 文件,
用 <link rel="stylesheet" href="/s.css"> 引进来。后者是常规做法——一个文件管全站,改一次全都变。
Demo 里那页 HTML 就有一行 <link>。
它不阻塞内容显示,但会阻塞「好看地显示」——浏览器拿到 CSS 之前不敢乱画,否则会闪一下变样。
二、选择器怎么命中
最常用的四种:元素名 p、类 .lead、id #top、后代 body p。
再加上属性 [type=text]、伪类 :hover,日常够用了。
类是可以复用的,一个元素可以挂好几个类;id 在一页里只能有一个。
所以类用来「归类」,id 用来「指认唯一的那一个」。
后代选择器 body p 的意思是「body 里面任意深度的 p」,不是「body 的直接子元素」。
要限定直接子元素得写 body > p。
这两个差别在深层嵌套时特别明显,也是样式「莫名其妙不生效」的常见原因。
三、谁赢:三元组说了算
特异性算成一个三元组:a 是 id 的个数,b 是类/属性/伪类的个数,c 是元素名的个数。
比大小就按 a、b、c 的顺序逐位比。
Demo 里三条规则都命中同一个 <p>:#top 是 (1,0,0),p.lead 是 (0,1,1),body p 是 (0,0,2)。
第一位 1 大于 0,后面两位再多也没用——所以 #top 赢,颜色是红色。
三元组完全一样时才看源序:后写的赢。这就是「越靠后越优先」这句话的准确版本——它只在平手时成立。
还有一条比 a 更硬的:!important。它直接跳过整套算法。
所以它一出现,后面的样式就变得不可预测——不是不能用,是用完要记账。
四、动手 Demo:把优先级算出来,而不是猜
写一个算特异性的小函数,把五条选择器的三元组都算出来; 再让三条规则同时命中一个元素,看程序判定谁赢。
def spec(sel):
a = sel.count(u'#')
b = sel.count(u'.') + sel.count(u'[') + sel.count(u':')
cc = 0
for p in sel.replace(u'>', u' ').split():
p = p.split(u':')[0].split(u'.')[0]
if p and not p.startswith(u'#') and p.isalnum():
cc += 1
return (a, b, cc)
实跑输出:
--------------------------------------------------------------------
CSS 优先级:谁赢是算出来的
--------------------------------------------------------------------
特异性算成一个三元组 (a, b, c):a 是 id 个数,b 是类/属性/伪类个数,
c 是元素名个数。比大小就按 a、b、c 的顺序逐位比,和数字大小一个道理。
选择器 (a, b, c) 声明
#top (1, 0, 0) color: red
p.lead (0, 1, 1) color: blue
body p (0, 0, 2) color: green
p (0, 0, 1) font-size: 14px
h1, p (0, 0, 1) margin: 0
同一个 `color`,三条规则都命中 `<p id="top" class="lead">`:
#top (1, 0, 0) color: red
p.lead (0, 1, 1) color: blue
body p (0, 0, 2) color: green
算出来的赢家 #top
它的三元组 (1, 0, 0)
`#top` 是 (1,0,0),`p.lead` 是 (0,1,1),`body p` 是 (0,0,2)。第一位一比,1 大于 0,后面两位再多也没用。
所以最后是红色——不是因为写得靠后,是因为它更「具体」。
如果两条规则的三元组一模一样呢?
p.lead (0, 1, 1) color: blue
p.lead (0, 1, 1) color: green
这时才看「谁写在后面」,后写的赢。所以平手时源序说了算。
五条选择器的三元组:#top (1,0,0)、p.lead (0,1,1)、body p (0,0,2)、p (0,0,1)、
h1, p (0,0,1)。
三条都命中同一个元素时,程序判定 #top 赢——因为它第一位就是 1,另外两条第一位是 0。
两条 p.lead 三元组一模一样时,程序按「后写的赢」判定,结果是绿色。这就是源序规则。
这个函数只处理了最基本的选择器,遇到 :not()、+、~ 会算错。
但日常九成的样式冲突,用它手算一遍就够定位了。
30 秒实验
- 把
!important加到body p那条上,看赢家会不会换。 - 写一条
div div div p,算算它的三元组,再想想它为什么常常「赢过」类选择器。 - 在浏览器开发者工具里点开一个元素,看 Styles 面板里被划掉的声明——那些就是输掉的规则。
常见误区
- 「后写的样式一定生效」——只在三元组平手时成立。
- 「id 比类优先是因为 id 更重要」——是因为它计在 a 位,权重更高。
- 「
body p只管直接子元素」——后代选择器管所有层级,直接子元素要写>。
自测三问(点开对答案)
1. 特异性三元组怎么算? —— (id 个数, 类/属性/伪类个数, 元素名个数),按位逐次比较。 2. `#top` 和 `p.lead` 谁赢?为什么? —— `#top`,它第一位是 1,对方是 0。 3. 什么时候才看「谁写在后面」? —— 三元组完全一样的时候。结构和长相都定了。页面要真正「动」起来,还得靠脚本。
- ☐ (本节完,下一节 10.2.1 让Web页面动起来的动态HTML)