14th century Abbot’s Fish House near Meare, Somerset, UK
The Echoplex

‘Q’ – A CSS Selector Engine

One of the best things to happen to JavaScript in recent times is the rise of CSS style selector engines.

The power of using an already familiar CSS syntax to gather elements is very satisfying, especially if previously you have been used to using bare-bones DOM methods. Their obvious appeal has made them central to most of the major frameworks in use today, and although they have been around for some time, a battle still continues within the developer community over which implementation is the fastest and most complete.

The days for this ongoing contest are numbered now though – with the advent of the Selectors API, querySelector and querySelectorAll will soon be available in all major browsers. But, alas, the older browsers linger well beyond their welcome, so the non-native selector engines will endure, for a while at least.

Since I’ve been working on a library for my projects, and had been lacking a descent selector engine, I decided to have a shot at building one to suit my needs, and fit within the existing library code.

Introducing Q

Ideally I wanted the engine to follow as closely as possible the behaviour of querySelector(All) so its functionality would be consistent whether the native API is supported or not. This meant cutting out a few features that are fairly common in other selector engines, such as the non-standard [attribute!=value] (instead you can write :not([attribute=value])) and the :contains pseudo class which has since been removed from the specification. Otherwise the selector support is as complete as is possible, or sensible, using JavaScript:

  • *
  • div
  • div p
  • div > p
  • div + p
  • div ~ p
  • *[class]
  • *[class=foo]
  • *[class~=foo]
  • *[class^=foo]
  • *[class$=foo]
  • *[class*=foo]
  • *[hreflang|=en]
  • *:nth-child(n)
  • *:nth-last-child(n)
  • *:nth-of-type(n)
  • *:nth-last-of-type(n)
  • *:first-child
  • *:last-child
  • *:first-of-type
  • *:last-of-type
  • *:only-child
  • *:only-of-type
  • *:root
  • *:target
  • *:empty
  • *:enabled
  • *:disabled
  • *:checked
  • *:not(div)

Some example usage:

// Function returns a static array of nodes
var collection = Q('#myid div.myclass');  

// Get first element in the list
var first = collection[0];  

// Loop using array methods
collection.each(function (el) {   
   // processing...
});

// Specify an initial context node (document is used by default)
var node = document.getElementById('myid');
var collection = Q(node, 'a'); 

// Complicated selector
var collection = Q('div+div+div>li:not(:last-child)');

How does it perform?

Q is pretty fast when run through the Slickspeed test. It also has good accuracy in returned result sets (more on this below).

From my tests, base2 and DOMAssistant usually come out top in terms of speed across a large range of selectors, but if you run the same test through your machine you will probably get different results depending on what computer, browser and platform combination you are using.

Lessons learned

Using the slickspeed test suite and testing engines in different browsers and machines has revealed some surprising results. Engines faster in one combination perform slower and/or turn up different results in other combinations. This makes the question of which one is ‘best’ a little slippery, even if basing judgement on speed alone.

The most crucial aspect, apart from speed, when judging an engine’s performance should be the accuracy with which it can return results across different browsers. You could argue that incomplete support for a selector that will rarely get used is not significant, but personally, I think it is. Technology should do what it promises; p:nth-last-of-type(-2n+3) may never get used, but knowing it’s avaliable makes a difference to how confidently I can use the API. At least there is then the opportunity to discover, through experience, where the real-world uses are for the more esoteric selectors.

Download

If you want to try the Q selector engine in your own projects, you can download it in standalone form on Google Code, licensing is MIT style.

Tagged with:

This is…

the online home of Pete Boere, web developer in Bristol, UK.

follow moi

code.google.com/p/jelly-javascript

Flickr photostream