编程在于折腾

从伪类和伪元素说起的知识点

本文于920天之前发表,文中内容可能已经过时。如有疑问,请在评论区留言。

在YY的一轮面试之后,和工作室的小伙伴一起讨论了“CSS伪类和伪元素”(因为就有一个面试问题是问“遇到过哪些伪类和伪元素,使用的场景有哪些?”),发现牵扯进来的边边角角还是挺多的,就用一篇文章来记录一下,如果有更多涉及两者的知识点,欢迎补充~

伪类和伪元素的区别

在刚开始学习前端的时候,我根本就没有注意到伪类伪元素说的是两个不同的东西,毕竟都是:xxx来表示。在MDN查询document.querySelectorAll()的API时,跳转到了伪元素

就像 pseudo classes (伪类)一样…

然后就意识到两者的不同,而且document.querySelectorAll()中文翻译也搞混了,英文版本的是说querySelectorAll()的选择器语法不支持伪元素而不是伪类。以伪类中的:target为例子,可以正确返回结果,见下图所示,可见两者还是很容易混淆的。

:target伪类

:target伪类

(有一个小插曲,想吐槽一下MDN。在很久之前就发现了翻译页面的错误,那时也想着改正,但是MDN编辑语法什么的真是orz,后来就放弃了,因为学习成本还算挺高的。一直到写这篇文章的时候,又记起这件事,实在忍不住,改正了翻译,不到100个字,花了我半个多小时…)

那么问题来了,如何区分两者?

一篇博客中的一张图已经为我们归纳了两者,

两者区别的归纳图

两者区别的归纳图

但是这并不是正确的打开方式,这只是知其然不知其所以然。

让我们回归W3C规范看看,规范是怎么对伪元素伪类进行定义的。

The pseudo-class concept is introduced to permit selection based on information that lies outside of the document tree or that cannot be expressed using the other simple selectors. 简单翻译:伪类这个概念的引入,是为了允许(authors)对那些在文档树之外或无法使用简单css选择器表示的信息(ps:个人感觉“文档树之外的状态”和“css选择器表示的元素”会更贴切)进行选择获取

Pseudo-elements create abstractions about the document tree beyond those specified by the document language. For instance, document languages do not offer mechanisms to access the first letter or first line of an element’s content. 简单翻译:在文档语言规定之外,伪元素创建了一个关于文档树的抽象引用。比如,文档语言并未提供获取元素的第一个字或者第一行的机制。

orz,差别在哪?

如果你想看看别人对两者的解读,可以看看stackoverflow这个回答以及评论区的讨论

按照我个人的理解:伪类侧重描述(descript,not filter)以不同于简单选择器选择存在的元素(比如:first-child)或元素的特殊状态(比如:hover)。而伪元素侧重的是虚拟的元素,比如:first-letter,就好像真实地存在文档树中一样,但实际上是不存在的,而:first-child指向的元素是存在于文档树的,这是两者的不同。这大概也是为什么querySelectorAll()适用于伪类而不适用于伪元素的原因吧–因为前者是真实存在于DOM树,但是后者并不是。

伪元素和伪类的选择器优先级

这方面的知识是在写这篇文章不久前才接触的。总的来说,伪元素的优先级和标签的优先级是一样的,而伪类的优先级和类以及属性选择器等等的优先级是相同的。

如果就这么简单地介绍完这个小节,那也就不必大费周章了。我们来解读W3C中关于选择器优先级计算的部分~

A selector’s specificity is calculated as follows:

  1. count 1 if the declaration is from is a ‘style’ attribute rather than a rule with a selector, 0 otherwise (= a) (In HTML, values of an element’s “style” attribute are style sheet rules. These rules have no selectors, so a=1, b=0, c=0, and d=0.)
  2. count the number of ID attributes in the selector (= b)
  3. count the number of other attributes and pseudo-classes in the selector (= c)
  4. count the number of element names and pseudo-elements in the selector (= d)

总的来说就是,每个行内样式(style="")以1000计,每个id选择器以100计,其他属性选择器和伪类选择器以10计,标签和伪元素选择器以1计,而通配符*0计。注意,这里只是提供一个计算的方便,并不是11个类的选择器可以将一个id选择器的样式覆盖掉。

还有一点,HTML行内样式可以被JavaScript的style覆盖,而JavaScript的行内样式可以被!important的样式覆盖。

官方还给出了一些例子:

*             {}  /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */
li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */
li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */
ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */
h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */
ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */
li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */
#x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */
style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */

层叠次序

这只是选择器Selector的优先级,而样式的确定不仅仅是由选择器的优先级这一个因素来确定,还有层叠次序。

W3C规范的6.4.1 Cascading order小节中有说明最终决定一个元素的样式的步骤:

  1. Find all declarations that apply to the element and property in question, for the target media type.Declarations apply if the associated selector matches the element in question and the target medium matches the media list on all @media rules containing the declaration and on all links on the path through which the style sheet was reached.
  2. Sort according to importance (normal or important) and origin (author, user, or user agent). In ascending order of precedence: 2.1. user agent declarations 2.2. user normal declarations 2.3. author normal declarations 2.4. author important declarations 2.5. user important declarations
  3. Sort rules with the same importance and origin by specificity of selector: more specific selectors will override more general ones. Pseudo-elements and pseudo-classes are counted as normal elements and classes, respectively.
  4. Finally, sort by order specified: if two declarations have the same weight, origin and specificity, the latter specified wins. Declarations in imported style sheets are considered to be before any declarations in the style sheet itself.

用图形来概括就是:

优先级

优先级

Declarations in imported style sheets are considered to be before any declarations in the style sheet itself.

英文水平有限的我,看到这句话的时候,一直搞不懂讲的是什么,误以为“import和link导入的样式会被看成是在<style>之前”。(不知道各位会翻译成什么样子呢哈哈)但是直到看Cascading Styles中讲到:

Note: If the link to the external style sheet is placed after the internal style sheet in HTML

, the external style sheet will override the internal style sheet!

决定用例子来试试,看看那句话说的是什么。

代码示例

代码示例

从图片中可以看出,样式最终选择的是blue,因为篇幅问题,大家可以在自己的电脑上试试,验证一下:优先级相同,以<style><link>的出现顺序来定义声明出现的顺序。

那最后的一句话真正的含义是什么?

我觉得应该是在讲,在同一个样式表中,@import的规则总是在其他规则的前面。

In CSS 2.1, any @import rules must precede all other rules (except the @charset rule, if present). – https://www.w3.org/TR/CSS2/cascade.html#at-import

【译】双语对照翻译测试文件

  1. 1. 伪类和伪元素的区别
  2. 2. 伪元素和伪类的选择器优先级
  3. 3. 层叠次序