This should really be Tip #1 since it’s the most critical of all. Let me just say this as clearly as possible:
Your fastest Javascript projects are the ones that have the least Javascript!
Sure, Javascript engines have matured. Safari’s Nitro, Chrome’s V8, Firefox’s TraceMonkey, and Opera’s Carackan all kick some serious butt. (IE8’s JScript, unfortunately, still sucks wind.) However, routines written in C++ still run orders of magnitude faster in most cases.
Don’t write Javascript to do something your browser already does at compiled-code speeds.
Do you have any idea now many person-years of effort went into the regular expressions engine in your browser? Tap into that resource!
Here are some other resources you may be under-utilizing:
- Array.prototype.join (example)
- Array.prototype.slice (example)
- RegExp (many string-related optimizations!)
- Array iterators
- DOM selectors (such as Acme (dojo) and Sizzle)
I’ll dive into each of these in later blog posts, but here’s a teaser. This example shows how to replace a relatively slow while
loop with an Array iterator.
Our while loop:
var nodes = dojo.query('*[title]'), // using dojo just as an example found, // we're looking for a particular node and will store it here len = nodes.length, // performance boost to grab this outside of the loop i = 0; // just an iteration counter while (!found && i < len) { // found will remain false until we find our node found = nodes[i].getAttribute('title').indexOf('Loading') >= 0 && nodes[i]; i++; } if (found) { // do something with <<found>> here } |
That’s not a bad while loop. You’ll notice that I am overloading the meaning of the found
variable. This is an acceptable practice in high-performance situations as long as the code is well-documented to prevent confusion.
Here’s the same loop utilizing the Array iterator some
wrapped by dojo NodeList’s some
method:
var found; // we're looking for a particular node and will store it here dojo.query('*[title]').some(function (node) { // found will remain false until we find our node found = node.getAttribute('title').indexOf('Loading') >= 0 && node; return found; }); if (found) { // do something with <<found>> here } |
Leaner! Faster! Sexier!
OK. Here’s the point where I scratch my head. I just ran both of these against dojocampus.org in FF 3.1 and Safari 4.0. To my surprise, the while
loop performed slightly better than the some
(1ms difference). I removed the [title]
attribute from the query selector to get some better test data. Same story.
There was a big difference in IE7, though.
I guess the latest Javascript engines really do kick butt!!!!
Update 2009-04-09:
My best guess is that the NodeList that dojo.query
returns is slowing our example code. A NodeList is not a true array. It is an Array-like object, which simply means it exposes its list of items as properties whose names are Cardinal number and also exposes a length property. So I created a quick and dirty test on a true array:
// using some var a = []; for(var i = 0; i < 100000; i++) { a[i] = new Date(2009, 1, i); }; var found, s = new Date(); a.some(function (item, i) { found = i == 99999 ? item : false; return found; }); console.log((new Date()) - s, found); // using while var len = a.length, found = false, s = new Date(), i = 0; while (!found && i < len) { found = i == 99999 ? a[i] : false; i++; } console.log((new Date()) - s, found); |
The results showed that some ran about 17% faster in Firefox 3.0, 32% faster in Firefox 3.1beta, and 43% faster in Safari 4.0beta. Of course, the code inside our loop is very simple. More complex code will consume a larger slice of the total time and will therefore make the effect of the loop mechanism less important.
Moral of the story: to get the best performance when looping through real arrays (rather than Nodelists or Array-like objects), use the Array iterators or their browser-safe counterparts in the major libraries. When using NodeLists, either method works great as long as you’ve tuned your loop correctly. (Sounds like another blog post…)
I’d like to hear from you! What library do you use and how does it handle Array iterators?