querySelector and querySelectorAll
Posted by David Smith on Thursday, February 7th, 2008 at 2:41 pmA familiar and unpleasant sight in web applications is fragile code traversing the DOM to get to certain elements or collections of elements. Chains of getElementById("something").parent.parent are all too common, hurting both readability and flexibility. As a result, many javascript libraries have implemented functions to use the powerful CSS selector system to look up DOM nodes.
Continuing the trend of standardizing and speeding up commonly used functionality from these libraries, WebKit now has support for the new W3C Selectors API, which consists of the querySelector and querySelectorAll methods. Hopefully libraries will begin to adopt this new functionality to provide performance improvements while remaining compatible with older browsers.
Some examples of how it could be used:
/*
* Get all the elements with class "hot" (duplicating getElementsByClassName)
* A common use for this is as a toggle;
* for example, a search feature might tag results with a class
*/
document.querySelectorAll(".hot");
/*
* Get the currently hovered element
*/
document.querySelector(":hover");
/*
* Get every other element in the <li> with id "large"
* This is mostly useful for doing "zebra stripe" alternating rows.
* Once CSS3 becomes more widespread, doing this directly via CSS will be more practical
*/
document.querySelectorAll("#large:nth-child(even)");
What would a new API like this be without benchmarks? The mootools project has conveniently created a wonderful test and benchmark suite for just this sort of functionality. A version of it that has been modified to test querySelectorAll as well as three common javascript libraries is available at http://webkit.org/perf/slickspeed/. Please note that many of the tests will only work in a build of WebKit from February 7th or later.
February 7th, 2008 at 6:44 pm
Fantastic news, David. I’ll get this into Prototype trunk as soon as I can.
Meanwhile: could you update to Prototype 1.6.0.2 on your benchmark page? For 1.6.0 we’d disabled the XPath approach to selector queries in Safari 3. We selectively re-enabled XPath in the latest release. I think it’d make a huge difference in the benchmarks.
February 7th, 2008 at 7:55 pm
heh, whoops. Disabling XPath would explain quite a bit! I’ll try to get a newer Prototype in there this evening.
February 7th, 2008 at 10:19 pm
Fixed. Helped quite a bit.
February 7th, 2008 at 10:32 pm
David, the link to W3C Selectors API is wrong. It should be: http://www.w3.org/TR/selectors-api/
February 7th, 2008 at 10:52 pm
Also fixed.
February 8th, 2008 at 1:30 am
That’s great news.. hopefully other browser engines will shortly catch up.
Most interesting part is ‘document.querySelector(”:hover”);’ this is impossible or at least very difficult to implement writing selectors support in JavaScript.. it opens new possibilities.
Anyway CSS selectors are still limited, you can’t go back in query.. e.g. you can’t query all p elements that contain a elements with XPath there’s no such limit. So hopefully popularity of selectors won’t decline XPath support.
February 8th, 2008 at 5:28 am
The two test pages, are showing contradictory results in Prototype/jQuery comparison. While there is no doubt for the overall winner.
This is probably du to the fact that mootools page use jQuery 1.2.2 while yours is 1.2.1. The last release has improved speed they says. Can you update to jQuery 1.2.2 so we can have the cutting edge comparison?
February 8th, 2008 at 6:21 am
Excellent news!
You guys are the bee’s knees!
February 8th, 2008 at 9:04 am
@Max_B: I’ve just updated to jQuery 1.2.3, which was just released today.
February 8th, 2008 at 11:04 am
I just realised that support for the :hover pseudo-class is mostly useless. The example given, document.querySelector(”:hover”), will always return the root element (or null), whereas the example was intended to return the element directly beneath the cursor (in other words, event.target if a “click” event was triggered). But it’s impossible to (reliably) discover this element because event.target is not necessarily the last element in the document.querySelectorAll(”:hover”) NodeList.
February 8th, 2008 at 12:05 pm
This is excellent news. What version of WebKit begins supporting this? It would be helpful when features like this are introduced to also include the build number and WebKit version so we can be sure to play with the right version.
February 8th, 2008 at 12:38 pm
Super awesome. Can’t wait to start using this (and I really hope it’s implemented into jQuery, my library of choice, soon).
Random question about the benchmark page: Why remove mootools? Just because ext is more popular and there was no room for a fifth column? As I said, I’m a jQuery man myself but it seems weird that mootools (the creators of the test, after all) didn’t make the cut to be included in the benchmarks.
February 8th, 2008 at 5:01 pm
dtetto: the benchmark, as checked out of svn, doesn’t have any libraries included. I just grabbed the first three I thought of.
February 10th, 2008 at 2:18 am
The SVN version and the Mootools one do different test sequences. Thats why the results differ so much. Anyway this Selector API is great.
February 11th, 2008 at 6:25 am
Obviously this is excellent news, made doubly so by the recent completion of all the CSS3 selectors.
However, I can’t help but point out one strange anomaly that I noticed when running the slickspeed benchmark – namely that while the native implementation blows away the js libraries, there is *one* test where it actually limps in in third place (on my MacBook 1.83GHz Core Duo, 2GB RAM, OS X 10.4.11)
Namely, #speech5
Webkit (r30123) consistently clocks in at 13ms as opposed to Ext at around 7ms and kQuery at 4ms.
I note that * is the fastest test at 12ms – does that reflect the overhead required for querySelector to kick into gear? Could querySelector not be tweaked to spot when an id is being requested by the document object and switch to getElementById?
February 11th, 2008 at 7:46 am
Unfortunately, my understanding of the spec indicates that despite the invalidity of multiple elements having the same id, we’re required to return all elements with a given id. That one blemish on the benchmark has been annoying me as well, but I see no way around it at the moment.
February 15th, 2008 at 7:09 am
David, you should submit a request that the specifications be changed. Having invalid documents on the web, namely ones with multiple identical IDs, hurts browser vendors and as a consequence, web developers and users. If querySelectorAll(”#someid”) was required to only return one result, and all major browsers obeyed this, then web developers wouldn’t be able to treat IDs as if they were classes and get away with it.
February 17th, 2008 at 6:10 am
It’s more complex than that; In order to do the return-one optimization for querySelectorAll, we would need to either special-case it (i.e. pre-parse the selector enough to determine if it’s an id selector alone), or make the regular CSS parser change its behavior. The former would slow down all non-id cases for querySelectorAll, the latter would break a bunch of web pages.
March 22nd, 2008 at 7:16 am
About querySelectorAll return value:
I think, that “length” property must not be enumerable (like in Array) to make “for (var i in result)” work the same way as “for (var i=0; i<result.length; i++)”