2009-05-31

Efficient JavaScript arguments manipulation

If you're a JavaScript aficionado, you already know about the "arguments" keyword that represents a reference to a pseudo-Array Object (but is not a proper Array Object).

But did you know that you can use Array.prototype's mutator methods on it without converting it to a proper Array (pop, push, shift, unshift, concat, indexOf (except in IE, which doesn't implement it natively))? Using the mutator methods with this technique actually don't mutate the original arguments pseudo-array, but a copy/representation of it. Conveniently, using such operations on "arguments" pseudo-Arrays is much more efficient than operating on true arrays (presumably because there's one (or more) fewer boxing operation(s) the interpreter/compiler must perform).

Browser Implementations

The first browser distinction is that Internet Explorer (IE6, IE7, IE8), Chrome 1.0-3.0, Firefox 1.0-3.5, and Opera 9-10 all report "Object" as the argument's class, but Safari 3.0-4.0 reports "Arguments". This is the only difference in Safari that I have found.

Interestingly, Chrome (and its underlying V8 JavaScript engine) keep the type of the object internally as "Arguments", but the Arguments.prototype has a .toString() method that replaces "Arguments" with "Object". See what your browser says: Object.getClass(arguments)

The object referenced by `arguments` has a prototype, but its prototype has no prototype (.__proto__ member) itself.

When I began optimizing my JavaScript deferred execution engine (that serves as the basis for my implementation of Microsoft's "M" modeling/programming language (part of the product(s) code-named "Oslo")), I learned that in order to use the arguments pseudo-Array like an Array, you have to convert it to an Array, usually using something like this:
Array.prototype.slice.call(arguments)

But since then I found a much more efficient technique that (thankfully) works in all modern browsers.

Using Array.prototype.pop and Array.prototype.unshift etc on JavaScript's "arguments"

I needed to pass (to another function) a copy of a function's arguments pseudo-Array, but with the final item stripped/popped. I ended up writing a popArgs() function that does the trick, *without converting the Arguments pseudo-Array to an Array.

Each of the Array.prototype's methods (in addition to the Array constructor itself) will actually work as expected when you invoke them with the `arguments` quasi-array as the context (or the arguments Array position in the .apply() method), like so:


function popArgs() {
[].pop.call(arguments);
return arguments;
}

function testArgsPop() {
var oldArgs = arguments;
var newArgs = popArgs.apply(null, arguments);
var oldArgsArray = Array.apply(null, oldArgs);
alert(oldArgsArray.toString());
var newArgsArray = Array.apply(null, newArgs);
alert(newArgsArray.toString());
}

testArgsPop(1,2,3);

// executing this snippet alerts "1,2,3" and then "1,2"

The popArgs routine above returns an "Arguments" object, and in order to alert() it, you have to convert it to a real Array.

The important thing to note in the above code is the Array.apply(null, arguments) invocation, which (Note: search the internet for "Array.apply" for previous work/discoveries on this technique) creates an Array from the arguments pseudo-array without using slice.

Performance Testing

I wrote up some performance comparison tests (careful to use generated arguments for each invocation to prevent unwanted (in this case) compiler optimization), and found similar results to others who have tested the performance differences between Array.apply(null, array) and Array.prototype.slice.call(array, 0).

In Firefox, IE, and Opera this is the fastest technique:

var argsToArray = (function() {
// cache the slice method in a closure
var Array_prototype_slice = Array.prototype.slice;
return function argsToArray() {
return Array_prototype_slice.call(arguments, 0);
};
})();

In Chrome (V8), using Array.apply (but not necessarily caching a reference to the Array constructor in a closure) is the fastest technique:

var arrayCreator = Array;
function argsToArray() {
// don't call argsToArray, just use arrayCreator.apply(null, arguments) directly.
return arrayCreator.apply(null, arguments);
}

In Safari (4 beta with latest nightly Webkit as of 2009-05-30), Array.apply(null, arguments) is fastest (without caching a reference to Array).

By the way, both Safari 4 and Chrome 3.0 (as of this posting) are 1 or 2 decimal orders of magnitude more efficient than IE8, Firefox 3.5, and Opera 9/10alpha at these operations.

2009-05-29

Obligatory first post.

Here lies a 'blog.