In JavaScript, the full collection of arguments passed into a function is made available through the special arguments
keyword. It's useful for fancy functional programming and writing functions (like Math.max()
) that apply to any number of arguments. It's also a little problematic because it's not an Array, but merely "Array-like." Let's cover how to work around this inconvenience and use arguments
to do some cool stuff.
What do you mean, "Array-like?"
arguments
has a length
and numeric properties (0, 1, 2, ...) so is similar enough to an array that this still works:
for ( var i=0; i <arguments.length; i++ ) {
alert(arguments[i]);
}
But if we check it's constructor, or try to use any of Array's methods, then we find that it's not really an Array:
arguments instanceof Array === false
arguments.slice === undefined
The ECMA-262 Standard, section 10.1.8, tells exactly what arguments
actually is:
-
It should inherit directly from
Object
. In particular, it is not, and cannot be, anArray
. -
It should have a
callee
property refering to the function itself. -
It should have a numeric
length
property. - It should have numeric properties 0, 1, 2, ... for each positional argument.
- None of these properties should show up when you loop over it.
I tried it for myself in Firefox 3.0, IE 6.0, and IE 7.0. Here's what I saw:
-
typeof arguments === 'object'
-
arguments
has all the required properties -
like other objects,
.valueOf()
returns itself -
like other objects,
.constructor
returns the global Object() constructor. -
arguments
has no iterable properties. thefor..in
operator onarguments
does zero iterations. -
However, you can use the
in
boolean operator to check for keys:-
'length' in arguments === true
-
'callee' in arguments === true
-
So, arguments
conforms to the standard in all three browsers.
Based on these facts, this function will detect arguments
objects in all browsers:
function isArguments(args) {
// typeof null is also 'object', but null throws
// a TypeError if you access a property.
// We check for it as a special case so we can
// safely use properties below.
if ( args === null ) return false;
if ( typeof args !== 'object' ) return false;
// make sure it has the required properties
if ( typeof args.callee !== 'function' ) return false;
if ( typeof args.length !== 'number' ) return false;
if ( args.constructor !== Object ) return false;
// it shouldn't have any iterable properties.
for ( var key in args ) return false;
// it passes all the tests
return true;
}
A fake "arguments" object created as { callee: function() {}, length: 0 }
won't pass this test because it's properties will be iterable.
Using Array Methods on Arguments
Even though arguments
is not a real Array, it's close enough that we might want to use array methods on it. I find myself using push()
, pop()
, shift()
and unshift()
on arguments
most often, and also the JavaScript 1.6 extension method forEach()
. There are two approaches:
- Copy it into a real Array
-
Apply Array methods directly to
arguments
.
The first seems to be more common; for example, in Prototype JS you use the $A() function.
If you're doing it yourself, the obvious approach works just fine:
function() {
var args = [];
for ( var i=0; i<arguments.length; i++ ) {
args.push(arguments[i]);
}
// ...
}
A slicker way (but not any faster, according to my informal benchmarking), is to borrow the slice()
method from Array:
function() {
var args = Array.prototype.slice.call(arguments);
// ...
}
Or even more concisely:
function() {
var args = [].slice.call(arguments);
// ..
}
That works becuase slice()
applied to an array returns a copy of it; we simply use the Function
class's call()
method to apply it to arguments
instead. However, if you're already comfortable borrowing methods from Array.prototype
, then there's actually no reason to copy arguments
at all. Instead, simply borrow the appropriate method from array as needed. For example, suppose you wanted to add an argument to the front and pass the arguments through to another function. To do this, you need the unshift()
method. You could copy arguments
into an array and then use the array's unshift()
method:
function() {
var args = [].slice.call(arguments);
args.unshift('first arg');
someOtherFunction.apply(window, args);
}
Or you could just cut to the chase and apply unshift()
directly to arguments
:
function() {
[].unshift.call(arguments, 'first arg');
someOtherFunction.apply(window, arguments);
}
Borrowing methods from Array will always work and results in less code. (I haven't measured performance; I'm sure any difference is negligable.) This is my prefered approach. The downside is that the code is less clear, particularly to beginners. If your team isn't comfortable with sophisticated JavaScript idioms I would avoid it. The clearest approach is to use a standard conversion function, like $A()
to explicitly create a real array:
function() {
var args = $A(arguments);
// ...
}
Functions With Variable Number of Arguments
The first use for arguments
that comes to mind is writing functions that accept a variable number of arguments, like Math.max()
:
Math.max(1,2,3,4,3,2,1) == 4
That function could be implemented as:
function max() {
var maxValue = -Infinity; // yes, that's Math.max() does too.
for ( var i=0; i<arguments.length; i++ ) {
if ( arguments[i] > maxValue ) maxValue = arguments[i];
}
return maxValue;
}
I don't think this idiom is very useful. In all cases, you can simply write a function that accepts an array instead, and it prevents you from adding optional arguments to your function later, which is a very common kind of code evolution. Also, it's not immediately obvious how to unwind it. Suppose you had an array of values that you wanted to apply Math.max()
to. How would you do it? Here's how:
var values = [1,2,3,4];
Math.max.apply(null, values);
However, most beginners (and even many surpisingly experienced JavaScript programmers) won't know to do that. As proof, here's a popular blog post that explains how to unwind one particular usage of the variable number of arguments idiom. So, it's an idiom that's supposed to make things more convenient, but actually makes things more confusing in complex cases. I would recommend avoiding it, although I grudgingly admit there are a few legitimate uses.
Transparently Passing arguments
Through To Other Functions
A better use of arguments
is passing it through to other functions, possibly with modifications. This is possible because of Function's apply()
method. apply()
takes two arguments, a scope and an array of arguments to invoke the function with, but it happily accepts an arguments
object. Here is the basic pattern:
function wrapper() {
return original.apply(this, arguments);
}
Let's say we call wrapper an pass in two arguments, 1 and 2. The wrapper turns around, and without even looking at them, calls original()
with the same two arguments. This works for any number of arguments, and we don't even have to know in advance how many arguments to expect. Whatever arguments wrapper()
is passed, original will be passed too. Note that in this example, we pass through this
as the scope, and return original()
's return value. The net effect is that wrapper()
behaves identically to original()
for all inputs. This is "transparent" in the sense that we don't need to know anything about original()
to wrap it. In fact, we can generalize this like so:
function wrap(original) {
return function() {
return original.apply(this, arguments);
}
}
wrap()
takes a function and returns a different function that nevertheless behaves identically to the original. By itself, that's not interesting, but wrapping a function gives us an oportunity to add features to it.
Lets do some examples. Suppose we wanted to add default arguments to a function:
function withDefaults(original, defaultArguments) {
return function() {
for( var i=0; i<defaultArguments.length; i++ ) {
if ( arguments[i] === undefined ) {
arguments[i] = defaultArguments[i];
}
}
return original.apply(this, arguments);
}
}
Or suppose we wanted to turn a regular function into a method (similar to PrototypeJS's methodize()):
function methodize(original) {
return function() {
[].unshift.call(arguments, this);
return original.apply(this, arguments);
}
}
This kind of generic functional programming is the reason JavaScript has the arguments collection. After all, the engine has to do extra work to set arguments
up for every function call; it wouldn't be worth it could only be used for writing variable number of argument functions. The real win is that functions can delegate some, most, or all of their behavior to another function in a transparent way.
- Oran Looney June 18th 2009
Thanks for reading. This blog is in "archive" mode and comments and RSS feed are disabled. We appologize for the inconvenience.