querySelectorAll on old IE versions: something that doesn’t work

(c) Bertrand Le Roy 2005 In today’s post, I’m going to show an interesting technique to solve a problem and then I will tear it to pieces and explain why it is actually useless. I believe that negative results should also be published so that we can save other people from wasting time trying the same thing. So here goes…

A few days ago, a post on Ajaxian proposed a new version of a somewhat old technique to implement querySelectorAll on old versions of IE, using the browser’s native CSS engine. That sounds like a great idea at first, and the hack is quite clever. The idea is to dynamically add a CSS rule to the document that has the selector that you want to evaluate, and an expression that adds the matched elements to a global array.

When I read this, it reminded me of a similar approach that I had tried a few years ago. At the time, we were considering implementing our own selector engine (we had not yet decided to integrate jQuery to our Ajax offerings, which in the end made the whole effort moot) so we explored a number of approaches.

My idea was different in that it doesn’t use expressions at all. It does dynamically create a style rule, but instead of an expression, it just sets a non-existing “foo” style property to the equally arbitrary value of “bar”. It then scans the whole document (using the much decried and IE-specific but very fast document.all) and gets the computed style for each of the elements. We then look for the foo property on the resulting object and check whether it evaluates as “bar”. For each element that matches, we add to an array.

Here’s the code:

(function() {
    var style = document.styleSheets[0] ||
document.createStyleSheet(); window.select = function(selector) { style.addRule(selector, "foo:bar"); var all = document.all, resultSet = []; for (var i = 0, l = all.length; i < l; i++) { if (all[i].currentStyle.foo === "bar") { resultSet[resultSet.length] = all[i]; } } style.removeRule(0); return resultSet; } })();

or, in minimized form:

(function(){var d=document;var a=d.styleSheets[0]||
d.createStyleSheet();window.select=function(e){
a.addRule(e,"f:b");var l=d.all,c=[];
for(var b=0,f=l.length;b<f;b++)if(l[b].currentStyle.f)
c[c.length]=l[b];a.removeRule(0);return c}})()

That’s 235 characters, which is not too bad (although not quite #twitcode small).

The first problem with that approach though is that because it’s using the native CSS selection engine in IE, it has the same limitations and quirks. That means no fancy CSS 3 (or even 2) selectors. It also means any IE bug will surface into the result set.

In other words, if you want more selectors than that, you will need to parse the selector string and branch off the code to another, more complete engine whenever something not supported is used. It also means that you need to know what is supported and what isn’t. That could be done through some dynamic discovery but doing so, we are getting into much complexity.

So limited as it is, how does it perform?

I ran the code in a SlickSpeed test (where I removed the selectors that it couldn’t handle) on IE6 and the good news is that despite the document.all scan and the current style computation, it’s more than three times faster than Paul Young’s implementation that got featured on Ajaxian.

But the bad news is that it’s also six times slower than jQuery:SlickSpeedResults

I’m afraid a hack to use the native CSS selection engine of the browser is always going to be slower than an optimized pure JavaScript implementation (to be clear, I’m not talking about native implementations of querySelectorAll, but about hacks such as this which try to surface the feature on older IE versions that don’t have querySelectorAll). Somewhat counter-intuitive, but true.

End of story. Just use jQuery. :)

5 Comments


  • I like this case of not having a choice )

  • Squeezed it down to 216, but still not quite #tweetcode small (damn it) :)

    (function(d){d=document,a=d.styleSheets[0]||d.createStyleSheet();this.select=function(e){a.addRule(e,"f:b");for(var l=d.all,b=0,c=[],f=l.length;b<f;b++)l[b].currentStyle.f&&c.push(l[b]);a.removeRule(0);return c}})()

  • Very interesting approach. Not that I want to introduce another query engine, or try to over-optimize what ultimately is a losing approach, but I was wondering if this approach could be further optimize your approach by limiting the elements to check. Perhaps by taking the last part of the selector and looking just for those tags, so "a.foobar" results in you calling getElementsByTagName("a") and checking just those.

    Anyways, thanks for sharing, I love hacky stuff like this. :)

  • @Nicholas: yes, you could, but doing so, you are starting to implement jQuery... Plus, I'm not even convinced that walking the DOM is the slow part: with a very simple DOM, you'll see that you can't get very much lower than 25ms per query (where jQuery wouldn't even take 1ms). That makes me think (although I haven't checked) that what takes time is actually that IE reflows the DOM whenever you touch the stylesheet, even though it shouldn't for unknown properties like we're using here.

  • I cant image MSIE is smart enough to cache the possible attribute for each element type so that it knows what should/shouldn't cause a reflow.

    For example, if the document was using a non-standard DTD or a custom xmlns, then it would be possible to use attributes which may have a visual effect (but which are not part of normal html standard/formatting).

Comments have been disabled for this content.