I cannot find in the official docs, or within any of the many articles I've read how CSS is parsed with compound selectors.
Side note: Obviously there are reasons for compound selectors in some specificity needs; and yes, descendant selectors are expensive.
Most articles simply validate that CSS is read right-to-left like this:
div.some-class li a
The authors of these articles state something like:
First, all anchor elements are matched, then the parser looks for a list-item as an ancestor, then it looks for an ancestor div with the class of "some-class."
In these descriptions, it is appearing that the CSS parser looks at space-separated combinators as single units instead of reading right-to-left within a given compound selector.
So a very common argument I see online and at work is that div.some-class
is faster than .some-class
because, "It only has to look at div
s that have that class." But, that would only make sense if CSS was read left-to-right, OR if in a compound selector there's an exception for better performance that it finds the element collection first before seeing if there's a matching class.
However, using the example above, my understanding is this:
All
a
elements are matched, then if there is anli
ancestor it's still matched, then it looks for ANY ELEMENT ancestor with a class of "some-class", THEN it checks if that element is adiv
. If so, the styles will be applied.
The real question:
1) Is div.some-class
still read right-to-left in that compound form; or,
2) as a compound selector, does the CSS parser find all ancestor div
s first, then see if they have that class?
An official source of the answer is what I'm most interested in.
Possible answer: Assuming the document.querySelectorAll
uses the CSS parsing engine and not a JavaScript "version" of it, I found the following:
Based on a test I did with 200,000
p
elements on a page, and all with the class of "p" on them. Querying.p
repeated in a loop 100X, vsp.p
showed that.p
is the fastest in Chrome 53. Selectingp.p
takes 1.71X as long. I repeated the process 8 times and averaged the numbers to get the difference..p
= 2,358 ms andp.p
= 4,036 ms.
function p() {
var d = Date.now();
var a = [];
var i = 0;
function fn() {
a.push(document.querySelectorAll(".p").length);
}
for (;i<100;i++) {
fn();
}
console.log(".p = " + (Date.now() - d));
}
function pp() {
var d = Date.now();
var a = [];
var i = 0;
function fn() {
a.push(document.querySelectorAll("p.p").length);
}
for (;i<100;i++) {
fn();
}
console.log("p.p = " + (Date.now() - d));
}
In Chrome 53, it appears that compound selectors are in fact still read right-to-left, making element.class compound selectors much slower than selecting by class alone, and the same with attributes instead of classes.
In IE11, it's mostly the inverse. Though not significantly faster, compound selectors with element.class
or element[attribute]
were actually faster than getting an the elements by class or attribute alone.