但是我最近读到大多数 CSS 选择器引擎从右到左读取,在这种情况下,第一个示例实际上不会更慢吗?
一般来说,CSS 选择器引擎的读取方式是什么?从左到右还是从右到左?如果他们通常从右到左阅读,有人可以向我解释为什么(我看不出从选择器引擎的角度从右到左阅读有什么意义)?
坦率地说,几乎不可能在给定的浏览器中判断哪个选择器会更慢,更不用说跨浏览器了。性能往往会波动和不可预测,尤其是在这种微观尺度和不可预测的文档结构下。即使我们谈论理论性能,它最终还是取决于实现。
话虽如此,如Boris Zbarsky's answer to this other question 和Guffa's answer to yours 所示,典型的浏览器(目前所有主要布局引擎都是如此)获取一个元素并评估所有候选选择器以查看它匹配的那些,而不是而不是找到与给定选择器匹配的一组元素。这是一个微妙但非常重要的区别。 Boris 提供的技术解释不仅非常详细,而且具有权威性(因为他在 Firefox 使用的引擎 Gecko 上工作),所以我强烈建议阅读它。
但我认为我应该解决您问题中似乎是另一个问题:
因为选择器引擎会简单地找到具有类名称的每个元素,然后必须识别其中哪些是divs?
还有Patrick McElhaney's comment:
链接的问题解释了为什么选择器通常从右到左读取,所以#foo ul.round.fancy li.current 被读取li.current, ul.round.fancy, #foo,但它真的在每个元素中从右到左读取(.current, li, .fancy, .round, ul, #foo)吗?应该吗?
我从未实现过 CSS,也没有见过其他浏览器是如何实现它的。我们从上面链接的答案中确实知道,浏览器使用从右到左的匹配来遍历选择器中的组合器,例如本例中的 > 组合器:
section > div.second > div.third
如果一个元素不是div.third,那么检查它的父元素是否是div.second 其父元素是section 是没有意义的。
但是,我不相信这种从右到左的顺序会一直深入到简单的选择器级别。换句话说,我不认为浏览器在 series 的从右到左评估中对简单选择器序列(也称为复合选择器)的每个部分使用从右到左评估em> 个由组合符分隔的复合选择器。
例如,考虑这个人为且高度夸张的选择器:
div.name[data-foo="bar"]:nth-child(5):hover::after
现在,不能保证浏览器会一定按以下顺序检查元素的这些条件:
- 指针是否在该元素上?
- 此元素是其父元素的第 5 个子元素吗?
- 此元素是否具有值为
bar 的data-foo 属性?
- 此元素是否有
name 类?
- 这是
div 元素吗?
除了简单的选择器杂乱无章之外,这个功能相同的选择器也不会必须按以下顺序进行评估:
div:hover[data-foo="bar"].name:nth-child(5)::after
- 此元素是其父元素的第 5 个子元素吗?
- 此元素是否有
name 类?
- 此元素是否具有值为
bar 的data-foo 属性?
- 指针是否在该元素上?
- 这是
div 元素吗?
根本没有理由出于性能原因执行此类命令。事实上,我认为首先选择某些类型的简单选择器可以提高性能,无论它们在序列中的哪个位置。 (您还会注意到 ::after 没有被考虑在内——这是因为伪元素不是简单的选择器,甚至从不进入匹配方程。)
例如,众所周知,ID 选择器是最快的。好吧,鲍里斯在他对链接问题的回答的最后一段中这么说:
还请注意,浏览器已经进行了其他优化,以避免尝试匹配绝对不匹配的规则。例如,如果最右边的选择器有一个 id 并且该 id 与元素的 id 不匹配,那么在 Gecko 中将不会尝试将该选择器与该元素匹配:尝试的“具有 ID 的选择器”集合来自对元素 ID 的哈希表查找。所以这是 70% 的规则,在仅考虑最右边选择器的标签/类/id 之后,很有可能匹配 仍然 不匹配。
换句话说,你是否有一个看起来像这样的选择器:
div#foo.bar:first-child
或者这个:
div.bar:first-child#foo
Gecko总是首先检查 ID 和类,不管它在序列中的位置。如果元素没有与选择器匹配的 ID 和类,那么它会立即被丢弃。如果你问我,那就太快了。
这只是 Gecko 的一个例子。这在实现之间也可能有所不同(例如 Gecko 和 WebKit 的执行方式可能与 Trident 甚至 Presto 不同)。当然,有些策略和方法是供应商普遍认可的(首先检查 ID 可能没有区别),但小细节可能会有所不同。