In which direction do selector engines read, exactly?
However I've read recently that most CSS selector engines read from right to left, in which case wouldn't the first example actually be slower?
Which way to CSS selector engines read in general? Left to right or right to left? And if they generally read right to left could someone please offer me an explanation as to why (I can't see how it makes sense to read right to left in terms of a selector engine)?
Frankly, it's nigh impossible to tell which selector will be slower in a given browser, much less across browsers. Performance tends to fluctuate and be unpredictable, especially at such microscopic scales and with unpredictable document structures. Even if we talk about theoretical performance, it ultimately depends on the implementation.
Having said that, as shown in Boris Zbarsky's answer to this other question and in Guffa's answer to yours, a typical browser (this is currently true of all major layout engines) takes an element and evaluates all the candidate selectors to see which ones it matches, rather than finding a set of elements that match a given selector. This is a subtle but very important difference. Boris offers a technical explanation that's not only incredibly detailed, but also authoritative (as he works on Gecko, the engine used by Firefox), so I highly suggest reading it.
But I thought I should address what seems to be another concern in your question:
As the selector engine would simply find every element with a class of name, and then have to identify which of those were
div
s?
As well as Patrick McElhaney's comment:
The linked question explains why selectors are read right-to-left in general, so
#foo ul.round.fancy li.current
is readli.current, ul.round.fancy, #foo
, but is it really read right-to-left within each element (.current, li, .fancy, .round, ul, #foo
)? Should it be?
I have never implemented CSS, nor have I seen how other browsers implement it. We do know from the answers linked above that browsers use right-to-left matching to walk across combinators within selectors, such as the >
combinators in this example:
section > div.second > div.third
If an element isn't a div.third
, then there is no point checking if its parent is a div.second
whose parent is a section
.
However, I don't believe that this right-to-left order drills all the way down to the simple selector level. In other words, I don't believe that browsers use right-to-left evaluation for each part of a simple selector sequence (also known as a compound selector) within the right-to-left evaluation across a series of compound selectors separated by combinators.
For example, consider this contrived and highly exaggerated selector:
div.name[data-foo="bar"]:nth-child(5):hover::after
Now, there's no guarantee a browser will necessarily check these conditions for an element in the following order:
- Is the pointer over this element?
- Is this element the 5th child of its parent?
- Does this element have a
data-foo
attribute with the valuebar
? - Does this element have a
name
class? - Is this a
div
element?
Nor would this selector, which is functionally identical to the above except with its simple selectors jumbled around, necessarily be evaluated in the following order:
div:hover[data-foo="bar"].name:nth-child(5)::after
- Is this element the 5th child of its parent?
- Does this element have a
name
class? - Does this element have a
data-foo
attribute with the valuebar
? - Is the pointer over this element?
- Is this a
div
element?
There is simply no reason that such an order would be enforced for performance reasons. In fact, I'd think that performance would be enhanced by picking at certain kinds of simple selectors first, no matter where they are in a sequence. (You'll also notice that the ::after
is not accounted for — that's because pseudo-elements are not simple selectors and never even enter into the matching equation.)
For example, it's very well-known that ID selectors are the fastest. Well, Boris says this in the last paragraph of his answer to the linked question:
Note also that there are other optimizations browsers already do to avoid even trying to match rules that definitely won't match. For example, if the rightmost selector has an id and that id doesn't match the element's id, then there will be no attempt to match that selector against that element at all in Gecko: the set of "selectors with IDs" that are attempted comes from a hashtable lookup on the element's ID. So this is 70% of the rules which have a pretty good chance of matching that still don't match after considering just the tag/class/id of the rightmost selector.
In other words, whether you have a selector that looks like this:
div#foo.bar:first-child
Or this:
div.bar:first-child#foo
Gecko will always check the ID and the class first, regardless of where it is positioned in the sequence. If the element doesn't have an ID and a class that matches the selector then it's instantly discarded. Pretty darn quick if you ask me.
That was just Gecko as an example. This may differ between implementations as well (e.g. Gecko and WebKit may do it differently from Trident or even Presto). There are strategies and approaches that are generally agreed upon by vendors, of course (there isn't likely to be a difference in checking IDs first), but the little details may differ.
The selector engine doesn't look for element to apply a rule, it looks for rules that apply to a specific element. Therefore it makes sense to read the selectors from right to left.
A selector like this:
div span.text a.demo
would make the selector engine do these checks to see if the selector applies to an element:
- Is it an
a
element with the classdemo
? - Does it have an ancestor that is a
span
element with the classtext
? - Does that element have an ancestor that is a
div
element?