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):
| iterations | prototype | shortcut | quick prototype |
Node.js 0.4.8 | 100,000,000 | 5019ms | 5075ms | 4692ms |
Firefox 6 | 10,000,000 | 1592ms | 2237ms | 1522ms |
Rhino 1.7 release 3 | 10,000,000 | 2318ms | 2687ms | 1878ms |
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).