jqWery
10/09/2011
I have prodigious, monstrous respect for selector engines. During the past few weeks, it was an absolute blast digging into Sizzle and qwery. I underwent an experiment to plug qwery into jQuery and see how far I could get in passing the 430 selector tests that jQuery currently has in its test suite. I have several discoveries and conclusions. In no particular order:
- qwery is not a viable selector engine for jQuery (wait, does that mean we're done? no!)
- Sizzle could be a lot faster and I plan on doing a minor overhaul of Sizzle and taking advantage of a few of the performance gems in qwery.
- qwery could be even faster and could learn a few things from Sizzle.
- Sizzle is a freaking beast.
Why won't qwery work?
- The chunker cannot handle multiple pseudos or attribute selectors. Supporting this would require a complete rewrite of the engine. qwery tries to split up a selection all at once and passes the parts to the interpret function, which is great but makes it impossible to support multiple pseudos or attrs. This is also explained on qwery's homepage.
- child selectors require a more complex filter and probably a prefilter in order to do things like :nth-child(2n+1) or :nth-child(-n+6), which qwery cannot do
- qwery would need very similar code to that which Sizzle uses to sort nodes based on document order when selectors contain commas. Currently the selections are simply concat'd. This makes for quicker selections, but would cause a lot of problems in jQuery (e.g. manipulating the DOM).
- Positional pseudo selectors like :eq or :first require different arguments than a normal pseudo and cannot be done until after the full selection is complete (additionally, as stated before, qwery can only handle one pseudo in a selection)
- Adding support for :not is more involved than it sounds. qwery does not currently support it.
- qwery required a significant amount of restructuring to work with jQuery(although that wasn't terrible) as well as a matchesSelector function to pass at least 50% of the jQuery tests. Until an addition that was landed today, there was no way for qwery to do a filter. You can check out how I expounded on that on my branch in order to make it work.
- Sizzle has the advantage of being live-tested about a million times more and can handle a vast number of edge cases without the hard struggle of reorganization. Just as an example, qwery cannot handle any selector that fails in querySelectorAll, even if it fails due to a browser's faulty implementation of querySelectorAll (of which there are quite a few cases). qwery does have a test for css3support, but that is only one of several use cases where querySelectorAll may fail (and misses out on using querySelectorAll in browsers that support it for less complex selectors). Another example is that older versions of Safari cannot handle uppercase values in its querySelectorAll. It will fail without throwing an exception so querySelectorAll should not be used at all in those versions of Safari.
- main point: qwery would undoubtedly have to look a lot more like Sizzle
Most of the above is intentionally outside of the scope of qwery; these are just a few of the reasons why qwery would not be feasible in jQuery.
Future Sizzle enhancements
- Currently, Sizzle rewrites it's main function based on the presence of querySelectorAll. This duplicates code and inconsistently optimizes for tags, ids, and classes in one, but not the other. We can fix this by doing something similar to qwery. Always optimize for tag, id, and possibly body, then call a select function that is querySelectorAll-dependent. This will speed up Sizzle and clean up some duplicate code in not only Sizzle, but also in jQuery's init function.
- Sizzle has an internal object called
Exprwith a ton of stuff on it. We should clean up internal references in order to.skip.the.vast.number.of.property.accesses. A minor optimization, but it should speed things up a bit. - When creating minimal, I learned that it is not worth it to have a
makeArrayfunction that attempts to useArray.prototype.sliceon nodes then provides a fallback. The simple array function is actually faster in all browsers. - Other code reductions and performance enhancements thanks to ideas from qwery's interpreter, caching system, and regex creation.
Some suggestions for qwery
- There is no need to have the cache Object with a getter and setter on a prototype. Save the function calls and simply use plain objects for your caches.
- Don't use an attrCache. qwery will get better performance with a ternary even when the regex has been cached. There are many things faster than a regex test so keep in mind the equivalent possibilities.
- Similar to attrCache, string concatenation + indexOf for testing classes is faster than regex testing even when cached.
- Consider catching the DOM exceptions on querySelectorAll(which are never descriptive), attempting the selection, then giving the user an idea of what went wrong. See the select changes and an example of using a qwery.error. You'll find this will not only give your user an indication of what funny selection was passed, but actually fix many selector bugs across browsers for free (no need for the css3support test anymore either).