1.过程
浏览器解析 CSS 选择器的过程大致可以分为以下几个步骤:
1.1 DOM 树构建
浏览器首先会解析 HTML 文档,构建 DOM 树(Document Object Model)。DOM 树表示了 HTML 文档的结构,每个 HTML 元素都是树上的一个节点。
1.2 CSS 解析
浏览器解析 CSS 样式表,将CSS 规则转换成浏览器可以理解的数据结构。这包括解析选择器、属性和值。
1.3 构建选择器列表
浏览器会将 CSS 规则中的选择器部分提取出来,形成一个选择器列表。每个选择器都会被解析成一个更易于匹配的数据结构,例如包含标签名、类名、ID 等信息。
1.4 从右到左匹配
这是 CSS 选择器匹配的关键步骤,也是性能优化的重点。 浏览器从选择器的最右边部分开始,向左逐步匹配。例如,对于选择器div.container p span
,浏览器会先查找所有 span 元素,然后向上查找其父元素是否为p
,再向上查找是否为.container
类名的div
元素。这种从右到左的匹配方式可以有效减少需要遍历的元素数量,提高匹配效率。
从右到左匹配的原因:
如果从左到右匹配,例如div.container p span
,浏览器需要先找到所有的div
元素,然后筛选出带有 .container
类的div
,再查找其后代元素中所有的p
,最后再查找p
的后代元素中的span
。这需要遍历大量的元素,效率低下。而从右到左匹配则可以避免这种大量的无效遍历。
1.5 生成样式上下文
当一个选择器匹配成功后,浏览器会为匹配的元素生成一个样式上下文。样式上下文包含了所有适用于该元素的样式规则及其对应的值。
1.6 应用样式
最后,浏览器根据生成的样式上下文,将样式应用到对应的 HTML 元素上,最终呈现出页面效果。
2.示例
在生成渲染树的过程中,渲染引擎会根据选择器提供的信息来遍历 DOM 树,找到对应的 DOM 节点后将样式规则附加到上面。来看一段样式选择器代码,以及一段要应用样式的 HTML:
CSS:
.box div span {
font-size: 20px;
}
HTML:
<div class="box">
<header>
<h3>
<span>这是一个标题</span>
</h3>
</header>
<div>
<ul>
<li><a href="http://">内容一</a></li>
<li><a href="http://">内容二</a></li>
<li><a href="http://">内容三</a></li>
</ul>
</div>
</div>
渲染引擎是怎么根据上述样式选择器去遍历这个 DOM 树的呢?是按照从左往右的选择器顺序去匹配,还是从右往左呢?
为了更直观的查看,我们先将这棵 DOM 树先绘制成图:
然后我们来对比一下两种顺序的匹配:
从左往右:.box => h3 => span
- 遍历所有的元素, 找有 .box 类的节点
- 从 .box 开始遍历所有的⼦孙节点 header、div 、 h3、 ul …
遍历所有的后代元素后, 知道了, 整个子孙后代只有一个 h3 - 找到 h3 , 还要继续重新遍历 h3 的所有子孙节点, 去找 span
- …
问题: 会进行大量树形结构子孙节点的遍历, 这是非常消耗成本的 ! 这在真实页面中⼀棵 DOM 树的节点成百上千的情况下,这种遍历方式的效率会非常的低,根本不适合采用。
从右往左:span => h3 => .mod-nav
- 先找到所有的 span 节点 ,然后基于每⼀个 span 再向上查找 h3
- 由 h3 再向上查找 .mod-nav 的节点
- 最后触及根元素 html 结束该分⽀遍历
…
从右向左的匹配规则, 只有第一次会遍历所有元素找节点, 而剩下的就是在看父辈祖辈是否满足选择器的条件, 匹配效率大大提升!