2011-08-01

JavaScript performance: Array.prototype versus []

Array.prototype contains many generic methods that can be applied to array-like objects. [] is a popular shortcut for accessing these methods. This post examines the pros and cons of using that shortcut.

Update: Inspired by a comment from Kevin Roberts, I’ve added a third way of accessing generic methods, and a conclusion.

Explanations

Array-like objects. Some objects in JavaScript are array-like, they have indexed access and a length property like arrays, but none of the array methods. Array-like objects include the special variable arguments (giving indexed access to all arguments that were passed to a function) and most DOM results. Not having the standard array methods is especially unfortunate under ECMAScript 5, which has goodies such as Array.prototype.forEach.

Generic methods. Some methods are generic. While they are directly available to instances of their prototype, they can also be borrowed by other instances. To borrow a generic method, one invokes one of the following two methods on it:

  • Function.prototype.call(thisValue, [arg1], [arg2], ...)
  • Function.prototype.apply(thisValue, [arrayWithArguments])
The borrowing instance is the first argument and becomes the value of this. Generic methods have to be written so that they require this to only have a minimal set of methods. For example, most generic array methods only need this to provide length and indexed access. Array.prototype.slice is generic and allows one to turn any part of an array-like object into an array.

Example: invoking Array.prototype.map() generically, on the array-like arguments object.

    function prefixHello(prefix) {
        return Array.prototype.map.call(arguments, function(elem) {
            return "Hello "+elem;
        });
    }
Interaction:
    > prefixHello("Jane", "John")
    [ 'Hello Jane', 'Hello John' ]

[] as a shortcut. [].foo is often used as a shortcut for Array.prototype.foo. That is, you access a prototype property via an instance.

  • Pro: More compact.
  • Con: Does not really describe one’s intent. You are not trying to invoke an instance method, you are borrowing a function from the prototype.
  • Con: Slightly slower (see below).

Timing several ways of accessing generic methods

I wanted to see how bad performance really suffered and did a quick non-scientific test. Test framework:
    var iterations = 100000000;
    var data = []; // empty so that slice() doesn’t have much to do
    (function () {
        var start = (new Date).getTime();
        
        // loop

        var diff = (new Date).getTime() - start;
        console.log(diff);    
    }());
Timing prototype access:
    for(var i=0; i<iterations; i++) {
        Array.prototype.slice.call(data);
    }
Timing the shortcut:
    for(var i=0; i<iterations; i++) {
        [].slice.call(data);
    }
Storing the prototype in a local variable:
    var arrayProto = Array.prototype;
    for(var i=0; i<iterations; i++) {
        arrayProto.slice.call(data);
    }
Results (iMac, 2.7 GHz Intel Core i5):

iterationsprototypeshortcutquick prototype
Node.js 0.4.8100,000,0005019ms5075ms4692ms
Firefox 610,000,0001592ms2237ms1522ms
Rhino 1.7 release 310,000,0002318ms2687ms1878ms

Conclusion

The time differences are not exactly earth-shattering. Thus, unless you are working with performance-critical code, you should choose whatever you think reads best (as opposed to what is easiest to write).

4 comments:

Jason Bunting said...

"Slightly slower?" I'd say!

kevnroberts said...

Did you try timing the shortcut without creating a new array in each iteration? Like so:
    (function () {        var start = (new Date).getTime()             a = [];        for(var i=0; i<iterations; i++) {            a.slice.call(data);        }        var diff = (new Date).getTime() - start;        console.log(diff);        }());

Axel Rauschmayer said...

Good idea. But a = Array.prototype makes more sense. I’ve updated the post accordingly.

kevnroberts said...

I was just wondering if the performance difference between the Prototype and the instance of Array (via []) was in the fact that you create a new array in each iteration. So at the end of the function, you have
100,000,000 anonymous empty arrays hanging around.I'm just curious if you used the same array object and used "slice()" from that one object instead of creating a new one every time, if that would increase performance or not.

Web Analytics