来自字节面试官的CSS优先级灵魂之问

234 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第5天,点击查看活动详情

前几天面试字节前端实习生,面试官一上来先问了CSS相关的内容,首先问了CSS选择器有哪些以及它们怎么计算权重,这一部分都是很八股的问题。然后又问了权重能不能进位,权重一样的情况以及如何修改,这一部分就超出了大部分CSS面经所涉及到的内容了。在这里,我查阅了MDN文档,尝试给出一个比较全面正确的答案。

回顾基础:CSS选择器种类

首先我们还是回顾一下:CSS有哪些选择器呢?这里我直接给出MDN的描述:

image.png

其中可能大家不太了解的是伪类和伪元素的区别。MDN中对伪类的描述是这样的:

伪类,用来样式化一个元素的特定状态

伪元素,用来选择一个元素的某个部分而不是元素自己

伪类(比如:hover)是一个元素在某些特定事件下的特定状态,这时应用的还是整个元素,只不过不是所有时候都应用。

而伪元素(比如::first-child)则是一个元素的特定部分,这时应用的是部分元素,但是是所有时候都应用。

回顾基础:CSS选择器优先级

关于CSS选择器的优先级,MDN是这样描述的:

一个选择器的优先级可以说是由四个部分相加 (分量),可以认为是个十百千 — 四位数的四个位数:

  1. 千位: 如果声明在 [style]的属性(内联样式)则该位得一分。这样的声明没有选择器,所以它得分总是1000。
  2. 百位: 选择器中包含ID选择器则该位得一分。
  3. 十位: 选择器中包含类选择器、属性选择器或者伪类则该位得一分。
  4. 个位:选择器中包含元素、伪元素选择器则该位得一分。

相信大家都背过这个规则,我们可以做一些联系来巩固对这套规则的记忆:

选择器千位百位十位个位优先级
h100010001
h1 + p::first-letter00030003
li > a[href*="en-US"] > .inline-warning00220022
#identifier01000100
内联样式10001000

还有一个特殊的 CSS 可以用来覆盖所有上面所有优先级计算,它就是!important

这些就是基础八股,这里只是简单的回顾一下。下面字节面试官的拷问才刚刚开始。

面试官:那么优先级能够进位吗?

我们都知道,在分析优先级的时候,很多文章(包括现在的MDN)是用个十百千位来介绍的。那么自然而然的我们就会有一个想法:如果低位的选择器超过了10个,能够进位从而使它覆盖更高位的选择器吗?

答案是否定的,我们可以自己写个小demo去检验这一事实,这里我就不写了,因为MDN显然也知道大家会有这个疑问,所以专门给出了警告:

image.png

所以,你会发现有一些文章是用(A,B,C,D)这种方式来介绍的,包括好像MDN的部分页面也是这样介绍的。这样介绍的好处就是不用纠结于进位之类的问题。

这样我们就解决了第一个稍微进阶的问题,但是水温才刚刚开始升高。

面试官:那么优先级一样会怎么样呢?

我们都知道,在浏览器需要渲染页面的时候,它会读取所有的CSS文件构建CSSDOM树,就是将所有的CSS文件解析了之后,计算每个DOM节点的具体样式信息。这个过程告诉了我们什么呢?css的样式是全部解析之后才计算的,也就是说,在同等优先级下,后面声明的样式规则肯定会覆盖前面的。

这是个很直观的思路,我们可以用codepen实验一下:

image.png

说真的,这题也不算难,因为绝大部分情况下(比如js中var同名变量的重复声明)都是后面覆盖前面。真正让我摸不着头脑的是下面这个问题:

面试官:我想让前面的同优先级选择器覆盖后面怎么做?

听到这个问题,我的第一想法就是!important,它并不直接参与在CSS优先级的计算之中,但是却可以覆盖所有规则,实现绝对的优先。如果能够使用!important,我们就可以给前面的选择器设置!important,后面的不动,让它实现覆盖:

image.png

然后当我说完答案,等待面试官的下一个问题时,面试官突然说:还有吗?

我最终没有想出来其他的方案,面试最后也忘记反问还有什么办法。这里给出我认为面试官可能想要考察的点:HTML解析顺序

权重相同时,与元素距离近的选择器生效?

首先我很确定:后面覆盖前面同优先级的选择器就是最终方案,因为再去增加更多的规则显然没有必要,也会导致混乱。包括上面用!important也只是一种取巧的思路,因为这就是最后兜底方案了。

那么即使有别的“让前面的同优先级选择器覆盖后面”的方案,也只是“看上去如此”,而并不是真的可以实现前面覆盖后面。那么我自然想到了一点:也就是看有些文章给出的规所谓“规则”:权重相同时,与元素距离近的选择器生效

首先给出结论:这个规则本身就是上一个规则。为什么这么说呢?那篇文章中给出了这样的例子:

image.png

作者认为:CSS样式表被html头部中的同优先级覆盖了,所以与元素距离近的选择器生效。但是真的如此吗?

并不是的,这是由HTML解析顺序造成的!

HTML会顺序的解析每一行,也就是从上到下,所以在更前面引入的CSS样式表被后面才出现的style内的样式覆盖了!这只是前面被后面覆盖的又一个例子而已,根本不是什么新规则!

我们可以用实验证明

考虑下面的代码,我们在style中规定颜色为蓝色,而在body之后的link中引入a.css,规定颜色为红色。

如果真的“因为html头部离元素更近一点,所以生效”,那么最终颜色应该是style中的蓝色。而如果只是根据解析顺序而定,最后的a.css中的规则会覆盖前面的style中的规则,所以最终应该是红色。

image.png

结果是:红色

image.png

在这个样式栈中我们可以看的更清楚:

image.png

所以根本就没有什么其他方法。要嘛是面试官在吓我,要嘛是面试官想考我用一些方法更改执行顺序,使得看上去后面的被前面的覆盖了(但是其实还是前面的被后面的覆盖了,只是声明顺序和执行顺序不一致罢了)。

以上就是关于css选择器权重/优先级最详细的解释。希望看完的朋友可以点个赞,您的支持是对我最大的鼓励。