Iterating Enumerable Objects

You cannot undervalue the cost-benefit of iteration, despite being such a simple construct, it is a frequently used process fundamental to any framework. For these reasons, we want to maximize the efficiency and effectiveness of the routine by leveraging native implementations where they are available. This is an important concept for all enumerable objects, not just arrays but also array-like objects such as nodelists and arguments. By harnessing the capabilities of the native environment we can utilize the improved performance and reliability inherent to the JavaScript engine.

Array-Like Objects

Array-like objects may look and behave like arrays on the surface; they have a length property and indexed values but that’s where the similarities stop. Where they differ is in the absence of the Array object constructor in the inheritance hierarchy of an array-like object. Subsequently, there is a lack of support for any native methods available to the array prototype. This means any array-like objects, in addition to a variety of other functions, does not support the Array.prototype.forEach method introduced in JavaScript 1.6 which is optimized for array iteration.

The most common types of array-like objects include the arguments object made available within the body of a function and DOM nodelists such as anything returned by document.getElementsByTagName().

Exploring Native Implementations

In addition to the methods added to the array prototype, Mozilla also implemented array generics to facilitate static method calls. These static methods treat any object passed to it as if it were an array so long as that object has a length property.

Included is the Array.forEach method, which is capable of performing iterations of arrays, nodelists, arguments, and even strings and functions:

Array.forEach(document.getElementsByTagName('div'), function(node, index){
    //do something
});

Unfortunately, these methods are a non-standard and therefore are only available to Firefox >= 1.5. For every other standards-compliant browsers such as Chrome, Opera, and Safari, we must resort to method hijacking. Here we will execute the Array.prototype.forEach method in the context of our enumerable object via the call method:

(function(){
    Array.prototype.forEach.call(arguments, function(item, index){
        //do something
    })
})(1, 2, 3, 4, 5);

Of course IE is not exactly standards-compliant, thus no native methods exist similar to the forEach method and we will have to resort to a for loop.

The Solution

When we combine these techniques, we get both a reliable and fast method capable of iterating various enumerable objects of differing types:

var each = (function(){
    if('forEach' in Array){
        return Array.forEach;
    }
    try{
        var forEach = Array.prototype.forEach;
        forEach.call(document.documentElement.childNodes, function(){});
        return function(o, fn, scope){
            forEach.call(o, fn, scope);
        }
    }catch(e){
        return function(o, fn, scope){
            for(var i=0, len=o.length; i < len; i++){
                fn.call(scope || o[i], o[i], i, o);
            }
        }
    }
})();

The function accepts an enumerable object as its first parameter followed by the function to be executed on each iteration of the object as the second parameter, and finally the context to execute the function in as an optional third parameter.

The method is capable of iteration on a wide variety of different types of objects, yet still handles them in a similar fashion. This makes it simple to iterate an enumerable object without knowing exactly what kind of object you are dealing with.

Conclusion

Leveraging native implementations where available, we can fully exploit the performance and reliability benefits attributed to the native environment. This helps to keep our iterations both simple and consistent for all enumerable objects and safe for all platforms.

This entry was posted on Thursday, January 13th, 2011 at 5:50 PM and is filed under JavaScript. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply