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])
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 |
4 comments:
"Slightly slower?" I'd say!
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); }());
Good idea. But a = Array.prototype makes more sense. I’ve updated the post accordingly.
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.
Post a Comment