This post has been a long time coming. It’s a combination of my distrust for JavaScript CSS selector performance analysis and my disdain for the CSS 3 Selector specification.
To start, I want to give a little bit of history regarding jQuery’s selector engine. When I first started working on its implementation it was mid-2005. It was mostly done as a personal challenge to myself – implementing a specification for kicks. You can see some of my early thoughts in a post that I wrote on Selectors in JavaScript. I completed my implementation on the same day as the first, other, JavaScript CSS selector engine: cssQuery. I then held of and merged it with some of my other efforts, which eventually resulted in jQuery. When I first implemented the engine, I went for full CSS 3 compliance (mashing in XPath-capable queries, as well).
Fast-forward 7 months and jQuery is starting to get a capable community. In preparation for jQuery 1.0 I decide to analyze the features of the engine to see what people are actually using. I ran a poll (which, unfortunately, has been lost) in which I asked the users which selectors they used. As it turned out there were a great number that no-one had any use for, whatsoever (and, in fact, this remains true to this day). At this point I removed them from the engine, breaking compliance with the CSS 3 Selector spec. Here’s some of the selectors that were removed:
- E:root – Rarely used in HTML. You already know what the root node is – it’s named ‘html’.
- E:empty – This might be useful if it could include empty whitespace text nodes, but it doesn’t. This will only match elements like <img/> and <hr/>, for whatever use that is.
- E:lang(fr) – This could be achieved in so many other ways – but in the end, how many multi-language-on-the-same-page sites are there?
- E:nth-of-type(n) – I’m not sure what the motivation was for creating all the -of-type methods, I’m sure it sounded great on paper, but in the world of HTML it’s not very useful.
- E:nth-last-child(n) – Another “great on paper” method. Don’t think I’ve ever seen it used.
- E:nth-last-of-type(n)
- E:first-of-type
- E:last-of-type
- E:only-of-type
- E:only-child – When does this occur? and why would you need to select it?
- E ~ F – Only selects adjacent elements, in one direction. Why a ~? Why only one direction?
- E + F – Only the next element – rarely useful.
- E[foo~=”bar”] – Only matches values in a space-separated list. This is only useful for classes (which is taken care of with .class) and the ref attribute. Why not just use *=?
- E[hreflang|=”en”] – Another selector that is really only useful for a single attribute – and not a popular one, at that.
What’s fascinating is that no one has ever, ever, requested that these features be added back in. They have virtually zero real-world use and applicability. In fact, with the exception of “E + F” all of these selectors were added, exclusively, in the CSS 3 specification. I’m not completely sure what the thought process was in selecting them, but it’s pretty obvious that it wasn’t grounded in application, but in theory (which isn’t really the spec-writers fault, considering that there were very few CSS 2-compatible implementations at the time).
Only later, after performance test suites started to arrive, did people start to care about the existence of – and the performance of – these selectors (and hence why selectors like +, ~, and [foo~=bar] now exist in jQuery).
To compensate for the shoddy offering of current CSS selectors, JavaScript libraries have had to write whole supersets of selector functionality to compensate for missing features. For example, jQuery includes both new selectors (such as “:hidden” and “:has()”) and new selector methods (like “.parent()” and “.prev()”) – all of which provide the user which phenomenally more functionality and clarity than the what is in CSS 3.
Now, I’m sure I’ll probably get lots of feedback saying “but ‘E + F’ can be useful, look at this example” or “of course ~= is useful, you can use it on rel attributes” – that’s not the point. The fact is that they are woefully un-used. To the point that they are a burden upon the implementors of the specification. What’s the point of implementing the above features – or more importantly: optimizing the above features for speed – if no one is using them.
Which leads me to my next bone to pick:
Performance isn’t Compliance
Everyone and their brother seems to use the SlickSpeed selector speed test suite. That’s fine, as far as implementation goes it’s a pretty good take on the matter. It runs quickly, spits out pretty results – users love it. However, it’s doing two things – and that’s one thing too many: It’s testing for both performance AND compliance of the selector engines. For example, if a user were to run the tests and see poor performance for, oh say, :nth-child(2n+1)
, they would be shocked, nay, appalled at the overall performance of that selector engine. But here’s the rub: That’s from a selector that is virtually un-used. (:nth-child is occasionally useful, in and of itself, but the An+B syntax is virtually worthless). But this is a point on which SlickSpeed does not care – since it’s also testing for compliance, in addition to performance, all tests are treated equally and “without bias.”
However, that’s precisely what isn’t needed: Selectors require bias. I’ve often argued that the speed of an ID selector is far more important than the speed of an attribute selector (for example) because of how commonly it’s used. However, up until this point I’ve never had data to back up this claim. I have resolved that.
I present to you the most commonly used CSS selectors used by jQuery from 59 of the most popular jQuery-using sites (which was borrowed from the featured sites list). (Fun fact: The use of $(DOMElement)
was more popular than all other selectors combined.) Here’s a small selection of the selectors found:
Selector | % Used | # of Uses |
---|---|---|
#id | 51.290% | 1431 |
.class | 13.082% | 365 |
tag | 6.416% | 179 |
tag.class | 3.978% | 111 |
#id tag | 2.151% | 60 |
tag#id | 1.935% | 54 |
#id:visible | 1.577% | 44 |
#id .class | 1.434% | 40 |
.class .class | 1.183% | 33 |
* | 0.968% | 27 |
#id tag.class | 0.932% | 26 |
#id:hidden | 0.789% | 22 |
tag[name=value] | 0.645% | 18 |
.class tag | 0.573% | 16 |
[name=value] | 0.538% | 15 |
tag tag | 0.502% | 14 |
#id #id | 0.430% | 12 |
#id tag tag | 0.358% | 10 |
View the rest of the selectors with a full explanation…
I spidered the JavaScript of all the sites, parsed through them, and found the appropriate selectors to compile the list. Here’s some things that I learned from the data:
There doesn’t seem to be a correlation between performance and selector use. For example, “.class” is far more popular than “tag.class” even though the second one is much more performant. What’s especially important about this is that the degree of performance hit isn’t that much of an issue. For example, the difference between 4ms and 30ms is virtually imperceptible. Instead there is an overwhelming trend towards simpler selectors. Obviously, user education could help, but it’s unclear as to how much that will change things in the end.
A couple of jQuery’s custom selectors are immensely popular: :visible, :hidden, and :selected. However it is unclear as to how useful they would be outside of a JavaScript-based CSS selector engine (there’s no real point in styling a hidden element).
A bunch of jQuery’s convenience selectors: :checkbox, :radio, :input, etc. would be quite useful, within a CSS selector spec – and it’s good to see them in wide use here.
There’s a bunch of unexpected queries that are used: “*”, “.class .class”, “[name=value]”, and “#id #id”. These types of queries are grossly under-represented in current performance test suites.
…there’s one thing that needs to be taken from all of this data, though: Speed test suites need to test reality rather than specification.
I’m perfectly ok with having two completely separate suites, one focusing on speed and one focusing on compliance, however mixing them does no one any favors: Users get a confused perception and suite authors (and browser vendors!) waste time dealing with optimizing things that don’t matter.
My proposal: A standardized performance suite (based on SlickSpeed, is fine) but populating the tests with comparable selectors to the ones shown above and weighted based upon their relevance. Thus, the speed of the #id selector should, actually, consume 51.29% of the total final score. This means that being 3x faster at this test would actually be 102x more important than becoming 3x faster at “tag tag”. This is absolutely not represented in any, current, test suites and needs to be rectified. Of course, I’ll be happy to seed my selector list with results from other popular sites of other selector libraries.
I’ll be fine constructing this suite, as well – I just want to make sure that there’s enough interest. I think this proposal has a lot of merit and should be strongly considered – the result will be a selector performance suite which will benefit everyone.
Kilian Valkhof (February 12, 2008 at 3:19 am)
I think that’s a great idea John. Statistics should practically always be weighed upon their relevance or else they mean very little.
I don’t have the knowledge to build something like that but lets hope that soneone that does sees this :)
John Resig (February 12, 2008 at 3:31 am)
@Kilian: I don’t think the implementation will be a problem, it’ll just be an extra column of data in the test suite – and I can definitely handle that! I’m more concerned about getting everyone to rally around it. If I build it and the other library, and browser, vendors simply poo-poo it, then what’s the point? We need to agree on a common base that we can all work against.
Patrick Wolf (February 12, 2008 at 5:02 am)
It’s always the same with statistics, they can really hide the truth.
Your approach of splitting the compliance- from the performance test and weighting the different performance tests according to there relevance would really help to get back the focus where it really makes sense to put effort into performance tuning of a Javascript library/browser/…
Why not have a mode/special version of jQuery where it can write some statistics about the usage of the different selectors. That’s probably more accurate that scanning some pieces of code.
Patrick
zimbatm (February 12, 2008 at 5:07 am)
On TAG.class vs. .class: I know TAG.class is good for speed but it is also needlessly limiting the DOM elements. When you’re creating your design, you find yourself switching from DIV to SPAN or other structures and it’s boring to scan the code for selector changes.
Tamlyn (February 12, 2008 at 5:10 am)
Pure, unadulterated sense, as always! You’d need a much bigger sample size to get a clear picture of the usage of the rarer selectors (plus, as you say, incorporate users of other libraries) but this is undeniably a step in the right direction.
Tane Piper (February 12, 2008 at 5:11 am)
Another excellent post John. I absolutely agree with you, as the browser moves from just rendering content to being a fully interactive framework to build applications in these speed issues become more of a concern, and test like this become more important.
If the other frameworks poo-poo the idea, I’d be more concerned with what they might be trying to hide?
Scott Hughes (February 12, 2008 at 5:25 am)
John, thank you for this very valuable research. I would expect the results to be similar for larger sample sizes as well.
There is one other reason why libraries should offer fewer selectors: to enforce the use of optimised queries. For example, I think a lot of people use “.class”-style selectors even when they know, for example, that only “li” elements will be matched, in which case “li.class” would do a far better job, even with XPath or using a native querySelectorAll API.
If I’m right, changing this coding practice (either by force or by education) could be an area for big performance gains. I think a small sacrifice of developer convenience for a better end-user experience is always an acceptable trade-off.
Kroc Camen (February 12, 2008 at 5:30 am)
People don’t use these selectors because they are held back by IE6.
There are great things you can do with the full CSS2 & 3 selectors, if you don’t have to even consider unsupported clients (iPhone web’apps’ for example).
I’m a bit reluctant to link to it, but check my name link to see the prototype personal website I’m developing that eschews IE-isms, and does make use of E + F, E ~ F and hopefully a lot more in the end. In the end, the site will be done with a self imposed limit of no more than 3 classes for the whole site.
When you get your head out of IE, then you begin to understand what these selectors are for, so their support is absolutely justified in frameworks that are then used in iPhone web apps, and places where full CSS support is known and guaranteed.
Sam Hasler (February 12, 2008 at 5:33 am)
Is there a danger that the pendulum could swing too far in the other direction. If the more performant selectors become more popular implementers will stop looking for performance improvements in selectors that could be used instead of them because they are not currently used.
Jörn Zaefferer (February 12, 2008 at 6:01 am)
I think its worth keeping in mind that those seemingly worthless CSS3 selectors are not designed for use in JavaScript. Take for :only-child as an example. With jQuery its easy to run a selector, then check the result size to decide how to deal with it, in this case, check size() == 1 and add some class or whatever. There is no way to accomplish that via CSS, is there?
Fun fact: “:blank” and “:filled” are custom selectors from the validation plugin, in use on newsweek.com. Looks like those are the only ones that made it to the top list.
I’d be interested in the script you used to generate those statistics. Could be helpful to tune applications.
Johan Sundström (February 12, 2008 at 6:18 am)
E[foo~=”bar”] is useful for <meta> tags.
Paolo Bonzini (February 12, 2008 at 6:18 am)
I used dt+dt in CSS, but not in Javascript.
Ian M (February 12, 2008 at 7:01 am)
Daniel Glazman just posted a rebuttal here: http://www.glazman.org/weblog/dotclear/index.php?post/2008/02/12/CSS-3-Selectors
Mariusz Nowak (February 12, 2008 at 7:35 am)
I think selectors in JavaScript are used a bit different than in CSS, and I wouldn’t question real-world use of some of them because they’re not useful in JavaScript.
In CSS we use more generic approach to elements. e.g. we want to style every even row of table.. we want to add margin to whatever element next to x.
In JavaScript when we want to do something on element we know more specifically what element we want to address, so it’s not likely to use more generic selectors.
I can hardly find use case for e.g. attaching mouse events to every second row in table.
The big drawback with Selectors in general is that you can’t go back in query (and I miss it when I work both with CSS and with JavaScript). It would be great if next version of Selectors would open doors for it but it’s not likely as it would complicate implementation of CSS engines in browsers.
I don’t like idea of :visible and :hidden selectors. Selectors should only address semantics or state of element not it’s presence.. I think making use of it is same kind of error as e.g. addressing a class name ‘red’.
Other thing which may have impact on your stats is that probably big number of jQuery users don’t know selectors beyond what IE6 engine supports. I have approached this problem in place I work now. Many programmers don’t know about child combinator, they don’t know how to use attribute selectors. Many times they also think that selector engine in JS reflects implementation of CSS engine in browser, so they rather not use CSS3 syntax as they’re afraid that it won’t work everywhere.
dunlop (February 12, 2008 at 8:02 am)
I would be interested in seeing the weighted results set of selector usage. I think what would also be useful in addition to comparing libraries would be to give users a tool that would show the frequency of selectors used in their code. This could allow library users to get a good representation of how their selector usage could be improved in addition to allowing them to see how other Libraries would preform in the same situation.
Nox (February 12, 2008 at 8:06 am)
“A bunch of jQuery’s convenience selectors: :checkbox, :radio, :input, etc. would be quite useful, within a CSS selector spec – and it’s good to see them in wide use here.”
input[type=checkbox], input[type=radio], input[type=text] :)
Robert Accettura (February 12, 2008 at 8:53 am)
Great post. Personally I always found the selectors exposed by jQuery to be a real strength.
I don’t think I’d ever have a use for most of those. The problem I see is that many developers rarely control all the markup. Between various designers and content contributors of various sill sets there’s a lot of code that’s really not that good. These often require somewhat obscure attributes to be specified (lang). While clever in theory, in practice I just don’t see it happening in most places. It’s hard enough to get text properly enclosed in a
<p/>
.Just my $0.02
Karl Swedberg (February 12, 2008 at 9:04 am)
This is an excellent post, John! I share your concern about the indiscriminate use of the slickspeed test. It’s interesting to see that, as ajaxian reported yesterday, even the webkit folks are using it. I’ve aired my grievances about slickspeed elsewhere, so I won’t go into it again here, but if another test is being developed, I hope it would at least address these other three problems with the current implementation:
1. use a more “real-world” DOM structure to test on, whatever that may be.
2. provide an average speed (or median, or whatever the statistically appropriate method is). That way we can remove any “error returned” results from the “offending” library’s score and still have an accurate idea of overall speed. Currently on Webkit’s slickspeed test, errors are calculated at 0ms – 2ms.
3. have a separate accuracy score. While slickspeed gives a special background color to rows where differing results appear, it doesn’t say anything about which one(s) got it right. Why should we accept a selector speed when it isn’t accurately selecting?
One note about the use of selectors in jQuery: In addition to the education issue that you mention and Mariusz Nowak elaborates on above, I think some are overlooked because they have a functionally equivalent method with a more intuitive name. Take the “E + F” selector, for example. I think it would be used a lot more — and people would be clamoring for it — if they didn’t also have $(E).next(F).
Thanks for this great post, John. As with most of your posts, it has not only addressed relevant issues in a thoughtful manner, but also sparked excellent follow-up discussion.
Karl Swedberg (February 12, 2008 at 9:17 am)
@Nox: I see your point about the first two jQuery convenience selectors already being covered by the attribute selector. The third one, however, has no equivalent CSS selector.
:input
is not the same asinput[type=text]
, since:input
selects all input elements, regardless of type, as well as textarea, select, and button elements. It’s an incredibly valuable selector when working with form data.kL (February 12, 2008 at 9:55 am)
I use + selector for selecting nth table column in browsers that don’t support :nth-of-type.
It’s also very useful for adjusting margins in header + paragraph scenario.
:empty is great for lists:
ul:empty {content:”Nothing here! Go home!”}
These might not be useful in JQuery, but don’t forget that these are CSS selectors, not JQuery selectors.
Dan G. Switzer, II (February 12, 2008 at 10:11 am)
It’s surprising to me that “#id #id” is such a commonly used selector, since it sort of goes against the spec. The “id” attribute is suppose to be unique per document, so specifying “#id #id” is overly verbose.
Of course in reality there are plenty of sites that re-use ids within a document.
Eric Meyer (February 12, 2008 at 10:16 am)
John, according to my tests (see also http://meyerweb.com/eric/tools/css/diagnostics/demo-not.html), :empty matches elements that have only whitespace text nodes (or no nodes at all) for children. I’m not much of a fan of :empty for other reasons, but your assertion about it appears to wrong, unless I misunderstood what you were saying.
Neil Mix (February 12, 2008 at 10:21 am)
Great post John. The emphasis on metrics over usability is a problem that pervades the entire technology industry. I’m glad to see you speaking out against it.
I think the disconnect between the types of selectors available and their usage stems from the fact that CSS is intended for designers, people who may not have control over the content which they are styling. It’s not too surprising that in practice jQuery users tend toward simple selectors — they have full access to alter the HTML content they are selecting from. Rather than use a complicated selector, they’ll instead update the HTML/DOM to fit their purposes. A fact which only reinforces your point that obscure selectors need not be optimized or even included in selector frameworks.
Kurt (February 12, 2008 at 11:02 am)
Dan – “#id #id” can be helpful when you have elements on different pages that are semantically the same thing, but aren’t placed or styled in the same way.
Karl Swedberg (February 12, 2008 at 11:04 am)
@Dan: in general, you’re probably right that ‘#id1 #id2’ is overly verbose. However, when a script is being run on multiple pages, it could very well be that one page has #id2 outside of #id1 and another has #id2 inside of #id1. If the developer only wants to manipulate #id2 if it’s inside #id1, then it seems the ‘#id1 #id2’ selector would be useful, if not necessary.
Hope that makes sense.
damg (February 12, 2008 at 11:07 am)
How about implementing a system like the language shootout where you could dynamically enter weights based on the benchmarks that are important to you? You could also provide a useful default based on real-world usage as you suggested.
That way, for people who do want to use the lesser used selectors, they can judge each framework based on their own usage patterns.
It would be even cooler if the benchmark allowed you to enter a URL, and then spider the site’s JavaScript code and do a count of all selectors used. Then it could adjust the weights for you before running the tests, and remove all the guesswork! It would be neat if on top of the total times, you could get a result like “Your website currently uses Prototype but could be faster by switching to JQuery.”
Adam van den Hoven (February 12, 2008 at 11:51 am)
John,
I agree with you whole heartedly. nth-of-type and its friends have a feel of “Lets add it for completeness” which is great unless you are an implementer.
On thing that I’m always appalled at is that speed tests only include selectors that return nodes. If you’re going to do any sort of unobtrusive JavaScript, especially where you don’t even want to know which JavaScript needs to be applied on a page, having fast identification of “failed” selectors is vitally important.
Boris (February 12, 2008 at 12:24 pm)
John, there’s a big difference between “.class1” and “.class2 .class1” in terms of how they’d map to a page structure.
As far as :hidden and :visible go, I can’t even find any documentation that would tell me what they do. The one example in the jquery API docs makes it look like they basically select on the rendering tree, not the DOM (which CSS can’t do for obvious reasons).
I think some of the commenters here have it right when they point out that the use cases for selectors in a jquery-like context and selectors in CSS are pretty different; wanting to style every other, or every third, table row a different style is a very common designer request, but in a jquery context it’s not so much use.
Your bigger point about silly performance suites testing silly synthetic benchmarks is a very good one, though, and I agree with that 100%.
Toes (February 12, 2008 at 12:25 pm)
Is weighting by simple appearance in code enough? Aren’t some selections done just once and others done many times?
>>Obviously, user education could help, but it’s unclear as to how much that will change things in the end.
So educate. Why should I use tag.class rather than .class? Can someone show me both in code and remind me why I should choose one over the other?
I do the easiest thing I can. When I sweep through the code for optimization, I’m looking at MY code, not how I call jQuery. I understand my code. I don’t understand the workings of jQuery.
I wish there was an official jQuery daily tutorial blog that dealt with such issues.
Toes (February 12, 2008 at 12:45 pm)
I just ran the Webkit version of slickspeed on Firefox (Windows XP).
http://webkit.org/perf/slickspeed/
It’s slower to run because it does everything 250 times. jQuery tested out terribly slowly. It wouldn’t really matter HOW you weighted the results of what’s currently there, jQuery would come out on the bottom. For jQuery to do OK, you’d have to really load the page up with stuff jQuery excels at. Then you’d be accused of biasing the test to make jQuery look good. You may want to look at it to make sure they didn’t do something screwy.
You need to make EVERYTHING go fast, be tight and compact and maintainable, and be bug-free. That’s what writing a library is all about. You write it once, your users use it thousands or millions of times. It’s not an easy job, but that’s what’s expected.
John Resig (February 12, 2008 at 12:55 pm)
@Patrick Wolf: That might work. One thing that I like about my current technique (parsing the results offline) is that it can be fully automated, requiring no browser use. At this point it would be trivial to feed in some more URLs or different selector libraries – which is a good thing.
@zimbatm: Absolutely – I do end up using ‘.CLASS’ personally. I’m not hugely surprised by this – just that I don’t think other people would expect it (“think of the performance!”). When, in reality, performance isn’t everything.
@Tamlyn: Yep – I think I’ll start pulling down all of the sites from the Sites Using jQuery list, as a good test bed. That should give us a few hundred sites – which should serve as an excellent statistical bed.
@Scott Hughes: That’s quite true, in fact we’ve done some education up to this point via Learning jQuery.
@Kroc Camen: I disagree completely – if people were still stuck in “IE land” then they wouldn’t be using attribute selectors or child selectors – both of which are two of the most commonly used selector types in jQuery – and neither of which are available in IE 6.
@Sam Hasler: I’m not really asking for implementors to completely stop optimizing any unimportant selector, simply that they should deserve less attention – which makes sense. Why should time be spent optimizing :nth-child(2n+1) if no one is using it? Certainly, if cases were made for the use of a rarely-used selector, any implementor worth his salt would work hard to improve its performance. So, certainly, it could swing to far – but it by far a better direction to be swinging (everyone wins, as opposed to no one).
@Joern: Yep, you’re correct – some things simply are easier in JavaScript. But that’s sort of the point, as well: The CSS 3 selector spec is un-ideal for use within browsers as a lot of what it encompasses isn’t practical to JavaScript use and, in fact, is far easier and more intuitive to use in pure JavaScript.
@Johan, Paolo, and kL: Yep, as I mentioned in my post, however, it’s not that these selectors aren’t used it’s that they are woefully un-useful. Why optimize these queries if they’re hardly ever used by anyone? Or, more importantly, why compare frameworks on performance over them?
@Mariusz: Absolutely it’s used differently. However, JavaScript-or-not I definitely question the usefulness of selectors like :nth-last-of-type().
I understand your point about semantics, but that’s not always the most useful point. For example, there’s a popular jQuery plugin which add CSS selectors that allow you to select based upon style information. For example: [height>100]. Of course that is highly unsemantic, but it’s also terribly useful.
Also, see my comments to Kroc Camen regarding “IE 6” selectors.
@Nox: Precisely: The fact that you have to write input[type=radio] just to select a radio input is incredibly frustrating to a user – and a reason why these selectors are so popular in jQuery. It should be noted, though, that :input is not input[type=text] but all form elements (which isn’t possible to select, within a context, in CSS).
@Karl Swedberg: Thanks for your comments Karl! I definitely agree on having a separate accuracy score – and definitely agree that it doesn’t matter how fast your query is, if it’s not selecting the right thing. This is a big reason for pairing down what is actually analyzed and tested against by the frameworks.
@Eric Meyer: Yep, I was correct – in your test LI #4 doesn’t receive any styling, in Firefox 2, which is a mistake in the spec (IMO). Since users frequently include extra whitespace (unknowingly) it’s important to take that into account and handle it gracefully.
@Neil Mix: That’s true about CSS being for designers, however I feel that they did a woefully poor job at giving relevant selectors to use. For example, you can select children, but not parents, next element, but not previous, and next adjacent elements, but not previous adjacent. Much of this was done to the benefit of the implementors, not the user – and is a big reason why JavaScript libraries are now filling these gaps.
@damg: That’s a great idea! Very feasible to do, as well. I’ll have to see about the custom spidering, but I like that thought process!
@Adam: I couldn’t agree more! I’ve been writing some new tests in which I include a bunch of selectors that don’t match anything – the results are incredibly revealing.
@Toes: The issue is that user education will never go far enough. And what’s the point? Why should be educate people about using div.foo when it’s the problem of the limitations that we’re working, not the problem of the users. Users should feel comfortable using the selectors that they want, not the selectors that the libraries demand.
As far as it being “slow” on Slickspeed – as I mentioned in my post, it’s completely full of nonsensical selectors that are worthless to optimize – which is the whole point: We need to have a suite of numbers that actually makes sense to refer back to. I’m not asking anyone to load things in favor of jQuery, just to load them in favor of the user. Oh, and by the way, run your tests in Internet Explorer and you’ll see a very different story.
Eric Meyer (February 12, 2008 at 1:02 pm)
Crap, I did misunderstand you. That’s what i get for posting under the influence of NyQuil(tm). Sorry!
Now I only wish I could figure out why Camino (and I think some other Gecko browsers) overlaps list items 4 and 5 in that test page. My Bugzilla-diving skills are not what once they were…
Toes (February 12, 2008 at 1:02 pm)
I tried it in IE and Ext2 beat jQuery handily. Prototype, on the other hand, was so slow that IE kept asking me if I wanted to stop the script.
>>@Toes: The issue is that user education will never go far enough. And what’s the point? Why should be educate people about using div.foo when it’s the problem of the limitations that we’re working, not the problem of the users. Users should feel comfortable using the selectors that they want, not the selectors that the libraries demand
I think users are teachable. If I know that div.foo is faster than .foo, believe me I’ll use div.foo.
Toes (February 12, 2008 at 1:15 pm)
Suppose I have this html…
<table><tr><td>
<div id="info"></div>
</td></tr></table>
I would usually do $(#info). But also $(td#info), right? Or $(tr td#info)? I don’t even know if that’s the right syntax, as I always go right for the class or id. Or $(table tr td#info). Or should I change my html by adding ids to the table, tr, and td? Would referring to them by specific ids be the best?
I really don’t know. YOU know what jQuery does internally. I don’t. Therefore, I desperately want hints on optimizing. I won’t follow them due to deep understanding (I’m too busy to dig into your internals), but I can follow them if you give me a pattern. I’m guessing specificity is the key to speed. Would that be an accurate statement?
John Resig (February 12, 2008 at 1:23 pm)
@Eric Meyer: No problem – yeah, Gecko seems to collapse empty li elements. Never quite understood that logic.
@Toes: My point is that users shouldn’t have to know what the framework wants. The framework should know what the user wants and adapt itself to fit those needs. What’s especially important is that teaching someone to use “div.class” could actually be a very bad thing. We have the brand new getElementsByClassName selector coming out in Firefox 3 and Webkit in some future release. It’s going to be much, much, faster to do “.class” in that case than “div.class”. In this case, educating the user as to the weird intricacies of the engine has done nothing but to slow down their progress. Again: Frameworks should bend, not users.
Toes (February 12, 2008 at 1:37 pm)
>>@Toes: My point is that users shouldn’t have to know what the framework wants. The framework should know what the user wants and adapt itself to fit those needs. What’s especially important is that teaching someone to use “div.class” could actually be a very bad thing. We have the brand new getElementsByClassName selector coming out in Firefox 3 and Webkit in some future release. It’s going to be much, much, faster to do “.class” in that case than “div.class”. In this case, educating the user as to the weird intricacies of the engine has done nothing but to slow down their progress. Again: Frameworks should bend, not users.
Or you could teach me “div.class” and then strip out the “div” when you get to the point when you know “.class” will be faster. In the real world I _am_ going to try to figure out how to make my code go faster. In general I want to know how to make the slowest cases and browsers faster. Fast browsers like FF3 and Webkit aren’t where I concentrate my optimizing.
Mostly, I don’t much care about jQuery’s speed. It’s only in loops that I care. So what I’ll try a couple things and see what is fastest on the most ass browser. In the rest of the code, I’ll try to cut character to keep the file small.
But I have to know what to try. Even the idea that $(#info) and $(div#info) might have different timings is valuable to me. But there will only be a couple places in the code where I care. I suppose I could try $(td #info) or $(tr #info) or $(table #info) as well. jQuery is spinning through the DOM looking for possible matches, right? Any given page could affect things differently, so I don’t expect hard and fast rules. Like I said, I’m going to try things on MY page in the WORST couple of browsers.
The site I’m working on is extremely taxing on JavaScript and the browser. Small changes in the code can add up to seconds saved overall.
I’ve made little tweaky changes in my JavaScript to make my site faster. Why wouldn’t I make little tweaky changes in how I call jQuery? To you, $ is jQuery. To me it’s money.
Tom Moertel (February 12, 2008 at 1:59 pm)
When you write that selector performance should be weighted by relevance, how do you define “relevance”? From your “Popular Selectors” page, it seems that you are measuring relevance by counting the occurrences of each selector within a sample of source code (that one would hope is representative of all real-world code). But don’t those counts fail to account for the reality that each selector can be used in a block of code that is called many times? Even though a selector may be *occur* only once in that code, the code may be *called* many, many times, and thus the selector ought to be weighted by call frequency. How do you account for call frequency in your analysis?
Cheers,
Tom
John Resig (February 12, 2008 at 2:15 pm)
@Tom: You’re correct that it isn’t being measured through frequency of call – I assume that the developers are already doing a good job of caching their re-used requests. Instead, I measure frequency through use – which is where we see the very-popular selectors bubble up, like “.class”, “tag#id”, and “#id #id”. I would prefer to collect the statistics through common use, rather than through inefficient code (e.g. a user re-querying $(“div”) 50 times would heavily skew the results).
Toes (February 12, 2008 at 2:35 pm)
>>I assume that the developers are already doing a good job of caching their re-used requests.
Wait. You think the framework should be flexible and that the user shouldn’t worry about the performance and we shouldn’t worry about being educated in what’s fast, and then you expect us to cache the results of our requests?
I don’t even know how to cache jQuery requests. Did you pages you look at have common requests cached? I met many of them did _not_. Remember, when we get started with jQuery, it’s a black box to us for a while.
John Resig (February 12, 2008 at 2:38 pm)
@Toes: I’m talking about the difference between:
var item = $("#item");
item.foo();
item.bar();
or
$("#item").foo().bar();
both of which are forms of caching (the first via a JavaScript variable, the second via chaining). All of this instead of doing:
$("#item").foo();
$("#item").bar();
Which is obviously un-cached and inefficient (you don’t need to know any performance analysis to tell you that!).
Jakub Nesetril (February 12, 2008 at 2:44 pm)
John,
I think you’re mixing two things here – real-world usage of certain selectors and standard support.
I’d argue that it’s important to support a full CSS3 selector suite simply for the benefit of stabilizing the platform. For one, selectors that might seem useless in Javascript are useful in CSS. Second, I view beneficial if JS libraries support the same selector suite that browsers are working towards supporting, too. Third, the slow uptake of CSS3 selectors is due to the fact of poor support.
I would like CSS-fluent designers to be able to pick up jQuery and not worry about what subset is supported and what is not. Right now, jQuery implements a superset of what browsers do. Flipping the situation and having browsers with CSS3 support but jQuery supporting a subset wouldn’t help much.
If you seek to measure real-world library speed – a weighted test you propose is correct. But if you want to shape inter-library competition in the right direction, I would divide the weights a least by a factor of 10, if not drop them completely.
Toes (February 12, 2008 at 2:47 pm)
>>Which is obviously un-cached and inefficient (you don’t need to know any performance analysis to tell you that!).
Right, but how would I know you don’t cache at least the most recent one for me?
There’s really no way but experimentation on our part and education from the library makers. It’s much more efficient for documentation to guide us than for each one of us finding the same things out.
Now, obviously, it’s our responsibility to do some digging an to share with each other. But we’ll naturally gravitate toward the libraries with the best documentation, performance, and ease-of-use.
I’m not dissing you or jQuery. I’m just trying to show you where we newbies are at when we pick up a library.
I had no idea jQuery was not doing some caching until you just gave me that example.
It’s valuable for you to say this stuff. And the easier it is to find, the better.
Boris (February 12, 2008 at 3:14 pm)
John, :empty is just buggy in Firefox 2. See , which got fixed for Gecko 1.9.
Boris (February 12, 2008 at 3:15 pm)
Though child nodes that do contain only whitespace do make :empty not match… it’s a tough world, since the whitespace _could_ in fact be rendered, depending on the styles that are set (white-space: pre, e.g.).
Guy Fraser (February 12, 2008 at 3:22 pm)
I particularly like this idea and have one additional suggestion:
Have a “hints” column that shows faster ways to do a given selector for each selector.
Eg. ‘.class’ would be faster as ‘tag.class’ or (I think…) also ‘#id.class’ – lots of people who are interested in performance will look at the speed tester so why not teach them some new tricks while they are there? :)
It would be useful to get input from other libraries that offer css selectors – they could get their own lists of “most used selectors” from sites that use their libraries and then you could either work out average weightings across all the libraries, or weight each library differently based on the sorts of selectors that get used most by sites using that library.
As for the ~= being used to style things on rel attribs, I must confess to actually having used that on several occasions. It does have a valid use, although it shouldn’t be given much weight in a speed test as it’s such a rare occurrence.
Maybe speed tests should by default show and test just the selectors that are used in the wild, with an option to do the full compliance test for people who just want to check to see if a particular selector is reliable?
Tom Moertel (February 12, 2008 at 3:49 pm)
John, even if the assumption “that developers are already doing a good job of caching their re-used requests” is true, your counting method seems not to account for the fundamental practice of placing commonly used code — including selector calls — into functions that can be called numerous times. Under your measurement scheme, each selector in a function will be counted only once, even if the function is called many, many times in practice.
Could you instrument a version of jQuery to track actual selector usage? That way you’ll know how many times each selector is actually invoked at runtime, rather than attempting to estimate the value via source-code counting.
Cheers,
Tom
Glen Lipka (February 12, 2008 at 4:16 pm)
I agree with the post 1000%. Great job John.
It would be awesome to have a bot that crawled the web, like Google, and collected selector data.
Toes (February 12, 2008 at 4:44 pm)
Tom Moertel,
>>Could you instrument a version of jQuery to track actual selector usage?
That’s a great idea. Mostly I’d love to know what dumb or silly things developers are doing.
I just cached a couple of selectors in my code. I would have never thought of doing that. I guess I really was treating jQuery as a black box.
Karl Swedberg (February 12, 2008 at 8:55 pm)
@Toes,
I just put “caching selectors” on my list of tutorials to write for the Learning jQuery blog. Can’t promise I’ll get to it soon, but I’ll do my best.
timothy (February 12, 2008 at 9:05 pm)
Great Karl. I just did it a couple places in my code where it made most sense.
Michael Koziarski (February 12, 2008 at 10:09 pm)
Excellent post john. The same can be said of almost every other benchmark you read on the web. No one’s building helloworld.com so why do people insist on benchmarking their web framework’s ability to render a static string?
Isaac (February 13, 2008 at 12:36 am)
I’m not sure I buy this. A lot of people aren’t using some of the more obscure selectors *because* they’re slower. You can quickly fall into the trap of not improving it because no one uses it, and having no one use it because it needs improved.
Pablo Impallari (February 13, 2008 at 3:13 am)
I agree that we must compare the speed of the selector everybody uses.
But Isaac has a good point up there.
Cloudream (February 13, 2008 at 1:46 pm)
We should collect other libraries code to weight selectors since jQuery has never support some of them when we compare different libraries? And treat $(‘div’).next(‘span’) as div ~ span?
Peter Michaux (February 14, 2008 at 10:57 am)
Weighting seems like a good idea. Weighting by the number of times a selector is executed seems like it might be more appropriate then how many times a selector appears in code.
timothy (February 14, 2008 at 11:20 am)
I’m not even sure weighting makes sense. I think we should look at a bunch of real-world pages and see where the choke points are. I don’t really care if a certain selector that’s only used, say, during setup is 2 milliseconds or 6. But I do care about selectors that are commonly used in tight loops.
Diego Perini (February 14, 2008 at 3:43 pm)
Make more sense in an application than in a framework.
Should we also weight the use of DOM method’s and start to discard the unused one, or should we do it with Javascript too ?
Everybody has chosen their way to optimize the “nth” queries, especially depending on their needs. I have chosen not to extend the properties of elements that I don’t own as an example, while jQuery have chosen to extend these properties to avoid recounting them each time.
You have overlooked the importance of the applicability (does this exists?) of the methods, nowadays for example the jQuery is() method is not suitable for the event delegation everybody is trying, also it is widely used for that task.
I agree about speed tests alone do not say much.
Lars Gunther (February 16, 2008 at 6:20 am)
Quick note. Being a Swede I’ve found myself on numerous occasions doing bilingual pages. Writing “how many multi-language-on-the-same-page sites are there” strikes me as being slightly US-centric.
Rene Saarsoo (February 16, 2008 at 8:19 am)
I think your statistics of CSS selector usage might be a bit misleading, because you counted the total number of times a selector was used.
For example the star (*) selector. The table says it was used 27 times. But was it used 27 times on one page or once on 27 pages out of 59? The latter case would mean, that the selector is actually pretty important.
BTW, it’s interesting to note, that the usage of CSS selectors in JavaScript seems to substantially differ from their use in CSS: http://triin.net/2006/06/12/CSS#figure-30
Anyway, a really interesting study!
Alan Gresley (February 18, 2008 at 12:21 pm)
@John Resig
I don’t understand your reasoning by saying most of the CSS selectors are useless. Here is some of your list:
* E:root – Very un-useful in HTML. You already know what the root node is – it’s named ‘html’.
Does every document have HTML as the root element? Look at the source of this page.
http://annevankesteren.nl/test/css/at-rule/import-001.htm
* E:lang(fr) – This could be achieved in so many other ways – but in the end, how many multi-language-on-the-same-page sites are there?
Isn’t it correct to wrap foreign text in elements with a “lang” attribute.
<p>And in France they use the word <span lang="fr">école</span> for
school</p>
Is there other ways to do this?
* E:nth-of-type(n) – I’m not sure what the motivation was for creating all the -of-type methods, I’m sure it sounded great on paper, but in the world of HTML it’s not very useful.
* E:nth-last-child(n) – Another “great on paper” method. Don’t think I’ve ever seen it used.
For table elements maybe.
* E:only-child – When does this occur? and why would you need to select it?
Because you may have a situation.
<div>
<table>
<tr><td></td><td></td><td></td></tr>
</table>
</div>
I don’t have to give a class or ID to select the table
table: only-child ()
* E ~ F – Only selects adjacent elements, in one direction. Why a ~? Why only one direction?
Because it would create circular references.
* E + F – Only the next element – rarely useful.
What if another div element contained two tables and I wanted to select the later table
table + table ()
* E[foo~=”bar”] – Only matches values in a space-separated list. This is only useful for classes (which is taken care of with .class) and the ref attribute. Why not just use *=?
Please see test 20 and test 22 on this selector test.
http://css-class.com/test/css/selectors/attribute-match.htm
Now consider that some elements can have multiple classes. One div may have class x and y with whitespace and another div may have class y and z with whitespace. Then I can do this.
div[class~=”y”] {}
* E[hreflang|=”en”] – Another selector that is really only useful for a single attribute – and not a popular one, at that.
That could be simply:
p[lang|=”au”]
<p>His <span lang="en-au">cobber</span> yelled, watch out for the <span lang="en-au">bluey</span>,
luckily he was wearing his <span lang="en-au">daks</span> as he climbed into the
<span lang="en-au">tinny</span>.</p>
This link should help you translate.
http://www.koalanet.com.au/australian-slang.html
These selectors are very powerful and as web developers understand them better, they will be used much more often.
Regards, Alan
Alan Gresley (February 18, 2008 at 12:25 pm)
Opps I wrote:
p[lang|=”au”]
This should be
span[lang|=”au”]
:-)
Alan Gresley (February 18, 2008 at 12:31 pm)
@John Resig
* E:empty – This might be useful if it could include empty whitespace text nodes, but it doesn’t. This will only match elements like and , for whatever use that is.
You have a box on a page with a certain background and border but no content. A user selects a button and this generates content inside the box by script. My empty element would be a div.
Regards, Alan
fantasai (February 18, 2008 at 8:44 pm)
wrt adjacent sibling selectors: They’re useful for styling subheadings in HTML. Not that there’s really appropriate markup for subheadings in HTML
fantasai (February 18, 2008 at 8:48 pm)
(Take 2:)
wrt adjacent sibling selectors: They’re useful for styling subheadings in HTML. Not that there’s really appropriate markup for subheadings in HTML < 5, but depending on your situation
H1 + H1 { font-style: italics; margin: 0;}
or
H1 + H2 { font-style: italics; margin: 0;}
could be useful.
Also, controlling paragraph indentation:
p {
margin: 0;
}
p + p {
text-indent: 2em;
}
You could probably do interesting things with DL lists, too — particularly those that have multiple terms per definition or multiple definitions per term (or both). Put a border between definition sets or what have you.
As for *-of-type, Alan’s right, they’re very useful for tables. Alternating row background colors is the most common example.
Aquarion (February 19, 2008 at 4:36 am)
I disagree with your assessment that the fact they aren’t being used now is a good reason to ditch them. The lang=$value stuff I don’t like (I prefer a more generic $attribute=$value, which would also solve a lot of the custom :checkbox-style things) idea to solve the parent problem), but the reason most of them aren’t being used is simply that most CSS engines cannot cater properly for them, so they do not appear on developer’s radar as to what is actually possible. More people would use them if more things supported them, removing them breaks this feedback loop.
On the other hand, removing them from the central library, if it increases maintainability, stability and speed is possibly worthwhile.
Matt Wilcox (February 19, 2008 at 6:34 am)
In CSS I use the + selector a lot. It’s great for styling the first paragraph after a heading, which is something that print people have done for years. h1 + p { font-size : 120%; }
Short of dumping a class on the p there’s no other way to do that.
Not used it in jQuery yet, but it’s definitely a useful (and used) selector.
Lars Gunther (February 19, 2008 at 8:13 am)
I was doing a definition list today and wanted to apply special rules to the last dt. last-of-type would have been *the* best solution. Alas! It is not usable with but a few of todays browsers.
Can anyone fix a selector that would do this without adding a class to the last dt?
#mod_journal_test > dl > dt:last-of-type()
Kolja Ritter (February 19, 2008 at 6:01 pm)
I comply with the reactions of most of the people here. Most of the selectors aren’t used, that’s a fact. But the reason is not because they aren’t useful. Many examples have been given already (thanks to A. Gresley). Those selectors just haven’t made it into websites because they are only known to a handful of browsers. And sadly those browsers are only used by a handful of people.
I already ditched IE because Microsoft lacks the ability to use their brains and still aren’t able to make IE CSS2.1 compilant (how many CSS bugs are still in IE? 60?).
Firefox is currently my browser of choice, because it is following the rules (currently) and being highly flexible (due to lots of great AddOns).
I think it’s sad, that you rather decide to follow Microsofts example in not following standards instead of the example given by the Opera browser, which (in the current 9.5 beta Version) does accept and handle ALL CSS3 selectors.
By the way… In case you stick to your decision I guess Firefox will be the next Browser to be ditched (at least by me and most people I know who are designing websites).
Ignoring standards is not a way to make people happy. It’s not the task of browser programmers to decide which selectors should be available.
CSS2.0 included some features which were later found to be useless.
Theese features were not removed by the browsers themselves, they were removed in CSS2.1 which followed a few years later.
Why do you think you can value the relevance of CSS-selectors in times where these selectors are hardly accepted and interpreted by webbrowsers?
[little bit of sarcasm on]
Why don’t you ditch CSS3 alltogether? CSS3 selectors are used in less than 1% of websites… Doesn’t it mean CSS3 must be useless? Nearly no one uses it at all. So why don’t we stick to CSS2.1, or even better, ditch CSS2.1 too, and stick to CSS1.0
[sarcasm off]
I still wan’t to use Firefox and I wan’t to keep it. But if you think you can measure the relevance of CSS selectors today you are definitely following the wrong track. The time will come that CSS3 is widely supported and used. Keep your script until theese times. If you check the relevances of CSS selectors in 3 to 5 years, then we can talk about it again. But 2008 is definitely the wrong year for theese kinds of discussions.
Diego Perini (February 22, 2008 at 9:23 pm)
@Alan Gresley,
contrary to what it looks like from inspecting the source of the link you pointed out in Anne van Kesteren test, I can assure you that “THERE IS A DOCUMENT ROOT”, just go to FBug console and type “alert(document.documentElement)”, this is the only element you can trust to be omnipresent.
Much of my code for event delegation relies on that point.
@John,
many of the jQuery plugins (the accordions to mention) also depend on the selectors blessed as unimportant, and a common feeling seems to be that those are not used because they are not available in many browsers yet, for that same reason they are likely unknown to most.
Anna Graham (May 20, 2008 at 9:33 am)
It is very dangerous to assume something is not being used.
The danger is that folks simply say “this thing is bust and inconsistent” and move off to the next toolkit – they had no obligation to inform you.
I was pretty darn annoyed at the poorly handled deprecation of certain features in previous releases – dropping them instead if leaving them in with a deprecated warning for a few revs. Some of us like to use jquery in real-world applications, and this kind of surprise is really really bad. Maybe they were some of the ops you were sure no one was using.
Other random concerns:
– why does the selector “#foo > bar” fail?
– why is the performance of the UI kit (demos) so horrible?
I hope the answer to the 2nd question is not that users without a high performance video card should not be surfing the web…