My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, December 06, 2008

A fast and crossbrowser function to make an Array

An absolutely common task present in almost every library, is to transform a generic collection/list of objects or DOM Elements into a friendly Array.

Cases Scenario



  • It is possible to apply directly an Array.prototype.slice call to quickly return an Array

  • It is not possible at all apply every kind of Array prototype to the list



Cases scenario detection


When we execute a piece of JavaScript in a web page, we can assume that we have at least one DOM element in that page, as the script itself for example, so it is always possible to obtain a collection of elements calling document.getElementsByTagName("script") or a generic ("*") in this case probably superflous considering the latter assumption.

Cross browser and fast makeArray proposal



var makeArray = function(push, slice){
try{
// Andrea Giammarchi proposal
slice.call(document.getElementsByTagName("script"), 0);
return function(array, results){
array = array instanceof Array ? array : slice.call(array, 0);
results ? push.apply(results, array) : results = array;
return results
}
}catch(e){
return function(array, results){
if(!(array instanceof Array))
for(var ret = [], i = 0, length = array.length; i < length; i++)
ret[i] = array[i];
else
var ret = array;
results ? push.apply(results, ret) : results = ret;
return results
}
}
}(Array.prototype.push, Array.prototype.slice);


Summary


The try catch is executed only once, the Array prototypes are cached once as well to guarantee cross libraries compatibility. The function is assigned differently for those browsers that can apply the slice prototype to the collection and those that cannot (mainly Internet Explorer).
Accepted parameters are two, inspired by Sizzle function, to allow us to concatenate elements into a collection.

The collection is transformed into an Array only if necessary, since it does not make sense to perform a slice call in every case.

Compatiblity? It should be every browser that supports try and catch statement :-)

3 comments:

Anonymous said...

I use `document.forms` for this check. It's "smaller" and more compatible (i.e. DOM1, not DOM2) though hardly makes a big difference : )

See one of the tests here: http://yura.thinkweb2.com/cft/

Unknown said...

So the optimization is just to make a self-modifying function (at 'compile' time), isn't it?

I've been working a bit lately on browser based applications for IPTV STBs, which are painfully slow and I've applied that same pattern quite a bit and with great results.

Andrea Giammarchi said...

Guys I know this post is not such a revelation but there are still libraries that do not apply the "perfect" way to perform this extremely common task.

DrSlump, as you said, results in compile time are absolutely worthy while for kangax, this trick is mainly designed to convert HTMLCOllections into Arrays so the best way I know so far is to try directly the transformation with an HTMLCollection itself.

At the same time this convertion possiblities means a lot of things about the browser, so the makeArray is just one of those common function to define inside the try or the catch, am I wrong? :-)