By Jakob Westhoff, first published at Mon, 01 Jul 2013 08:10:13 +0200
Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.
You can also buy a printed version of the book on Amazon or on epubli.
arguments
-objectIncited by a tweet regarding JavaScripts arguments
-object, I decided to detail this object in this blog post. A lot of people are somewhat confused about the inner workings of JavaScript engines in general. Every now and then most JavaScript developers can't explain why certain things happen and others don't. One of those mysteries fueling the confusion is the arguments
-object. This post will help to solve this mystery once and for all.
arguments
-objectGet expert knowledge for the full stack of your web application architecture and software design.
Some people know it, others might never have gotten in contact with the mysterious object called arguments
.
It provides dynamic ways of accessing parameters provided to a function during calltime, besides other things. If any function needs to support an arbitrary amount of parameters, the arguments
object comes into play. A mostly well known example of this is the implementation of sprintf
. This function takes a formatting string as well as a variable amount of arguments, one for each part of the formatting string. Utilizing all of those informations it creates a formatted string output according to the given directions:
var sprintf = function(format /*, args... */) {
// Format string and output value
}
var formattedString = sprintf(
"Some embedded %s and a number %d",
"string",
42
);
>>> "Some embedded string and a number 42"
The arguments
-object is automatically available inside each invoked function. It can be accessed simply calling it by its name:
var sprintf = function(format /*, args... */) {
// Access the second argument, if it is there
alert(
arguments[1]
);
}
The sprintf
function is known from languages like C, where it is part of the standard library. Other languages like PHP do provide counterparts as well. JavaScript unfortunately does not have something like this built in. Therefore it needs to be implemented in the user land. Implementing the sprintf
function exceeds the scope of this blog post. However, if you are interested in how something like this could be accomplished, you are welcome to take a look at an sprintf implementation I wrote ages ago. It may be old, but it still does the trick.
arguments
Array-like BehaviorAs it is shown in the initial examples, arguments
can be accessed like an array. It is automatically filled with numeric indices, representing each of the parameters given to the called function.
Given a function call like this:
var formattedString = sprintf(
"Some embedded %s and a number %d",
"string",
42
);
The arguments
-object available inside the sprintf
function would contain the following numerical indices:
// Pseudo representation of arguments inside the sprintf function call:
arguments = {
0: "Some embedded %s and a number %d",
1: "string",
2: 42
}
Each of the provided parameters is referenced with a zero-based index from left to right. All given parameters are included, those who have a defined name as well as those who don't. Regarding the example this implies the first string "Some embedded %s and a number %d"
may be accessed using each of the following ways:
alert(format);
alert(arguments[0]);
The rest of the provided parameters may only be accessed using the arguments
object, as they haven't been given a proper name:
alert(arguments[1]);
alert(arguments[2]);
Note: In ECMAScript 6 it will be possible to name and directly access this kind of arbitrary length arguments using Rest Parameters. You may even utilize this feature today using Google's Traceur Transpiler
Okay, now we know about the arguments
object. At a first glance this thing seems to be simple enough. It's an array-like structure containing all the given parameters using a zero-based index.
Within this simple sentence the first catch is already hidden: arguments
may seem to be an array, but in fact it is something else.
arguments
is NO arrayThe arguments
variable may look like an array, but essentially it isn't. The confusion is understandable, though: A data-structure with zero-based numerical indices, which is accessed using the square-bracket notation ([n]
), surely does feel like an array. In fact, arguments
is a basic JavaScript object. As objects are mostly key-value pairs, there is no reason why an object should not have numerical keys.
Unfortunately, this behavior can cause some headaches, as in JavaScript arrays provide a certain functionality like concat
, slice
, splice
and others. Those methods would be quite useful for the arguments
object as well, but unfortunately are not available on that object, due to the fact that it is no real array.
Note: In JavaScript every array is essentially an object as well. However, it is an object inheriting from the Array.prototype
, which implies that all of the known functionality is available on every Array. With arguments
this inheritance connection does not exist. Therefore it is valid to say arguments
is no Array.
Calling one of those functions on the object causes a TypeError
:
function sprintf(format /*, args... */) {
alert(
arguments.slice(1)
);
}
sprintf("Foo %s Bar", "some string");
>>> TypeError: Object #<Object> has no method 'slice'
arguments
As mentioned before, in certain situations the usage of those array functions on the arguments
object could be a time saver. Luckily for us, JavaScript's prototypical inheritance system combined with the fact that JavaScript engines don't do strict type checking, embrace us with everything needed to call the slice
method (or any other array method) on arguments
:
function sprintf(format /*, args... */) {
alert(
Array.prototype.slice.call(arguments, 1)
);
}
sprintf("Foo %s Bar", "some string");
>>> ["some string"]
What exactly did happen here? First, the prototype
property of the Array
object is accessed. The contents of this property houses all the functionality that is inherited down to each array, which is created using either the square-bracket notation or the Arrray
constructor:
var squareBrackets = [1,2,3,4];
var arrayCtor = new Array(4);
alert(
Object.getPrototypeOf(squareBrackets)
);
>>> Array.prototype
alert(
Object.getPrototypeOf(arrayCtor)
);
>>> Array.prototype
If every method an array inherits is defined on this prototype
property, the slice
function obviously is stored there as well. We may simply access this function by using its name. One possibility would be to simply call the function utilizing the usual calling parenthesis:
Array.prototype.slice(1);
But on which data structure would this method be called on if we did it this way? Remember, under normal circumstances the method is called directly on the array which should be modified:
var arr = [1,2,3];
alert(
arr.slice(1)
);
>>> [2,3]
Due to this way of calling, the method has access to the array it should modify using the this
keyword. If it is called directly on the prototype
property, the method does not really know what to do.
Fortunately, JavaScript allows us to specify the value of the special variable this
, while a function called, using the call
or apply
method:
function f() {
alert(this.foo);
}
var obj = {
foo: "Some string"
}
f.call(obj)
>>> "Some string"
Using all of those techniques in combination, it is now possible to do what we essentially wanted in the first place:
Locate the slice
function, which is usable on arrays
Execute this function
Let this
be the array-like arguments
objects, we are currently working with
Array.prototype.slice.call(arguments, 1);
Why does this work if arguments
is no real array? It works, because JavaScript does not make further assumptions about the structure or type while processing data as an array. It works using DuckTyping. Everything that looks like an array can be used in conjunction with the array function. Something is supposed to be an array if it provides numeric keys as well as a length
property. As both of these assumptions are true for the arguments
object, everything is in order.
Another mostly confusing fact about the arguments
object is, that parameters are referenced, not copied. Therefore changing either the named argument itself, or altering the numeric index on the arguments
object will affect both incarnations the same way. Especially with this tricky constellation it is true that an example says more than a thousand words:
function f(a) {
alert(a);
>>> 23
alert(arguments[0]);
>>> 23
a = 1000;
alert(arguments[0]);
>>> 1000
arguments[0] = 42;
alert(a)
>>> 42
}
f(23);
This behavior is irritating for most JavaScript developers, as they are not really comfortable with the concept of real references, due to the fact that the language does not support them natively. Nevertheless, after knowing about that weird behavior, using arguments
is quite easily and productively possible.
Unfortunately, JavaScript's referencing behavior leads to another common pitfall: Declared, but not provided function parameters are not referenced.
JavaScript engines are establishing references only to provided arguments, therefore declared but not used arguments cause a problem:
function f(a, b) {
alert(arguments[1]);
>>> undefined
alert(b);
>>> undefined
b = 100;
alert(arguments[1]);
>>> undefined
arguments[1] = 23;
alert(b);
>>> 100
}
// The second parameter is intentionally not given
f(42);
This example demonstrates that index based references on the arguments
object are only created for parameters which are provided while the function is called. Other arguments are available within the function's scope using their declared name (b
) as an identifier. However, they are not connected to the arguments
object.
This behavior can cause major headaches as well as nights of debugging. A best practice which solves half of this problem is to access the arguments
object in a read-only fashion. Therefore one should never change or write to new values to the arguments
object.
Nevertheless, this does only solve one part of the problem. In order to fully solve every parameter, named ones as well as anonymous ones need to be accessed read-only. Even though this might seem possible, there are certain situations, where writing to function parameters is a perfectly fine usecase, for instance optional arguments with default values.
Essentially, the solution boils down to minimizing the write access to named arguments, while never writing to the arguments
object itself.
arguments
objectUnfortunately, the attempt to solve the problems with the argument
object may cause even more confusion. Within ECMAScript 5 Strict Mode the before described referencing behavior is completely removed. Essentially being replaced by using shallow value copies instead of references within the arguments
object.
The Strict Mode has been introduced with the latest finalized specification of the ECMAScript/JavaScript language. In conjunction with certain security related functionality, it does mainly provide automatic checks of common syntactical pitfalls, which were valid JavaScript Syntax before, but should never have made it into the language in the first place:
Different function arguments with the same identifier
function f(a, a, b) {/…/}
Object literals with multiple properties using the same name
var obj = {a: 1, a: 2, b: 3};
Redefining language constructs
NaN = "something else";
…
Note: Strict Mode should be enabled in every modern JavaScript project to improve code quality and reduce common syntactical mistakes.
Once Strict Mode is enabled, the arguments
object looses all of its referencing magic. Access to numeric indexed parameters is provided through simple shallow copies of the given arguments:
function f(a) {
"use strict";
alert(a);
>>> "foobar"
alert(arguments[0]);
>>> "foobar"
alert(a === arguments[0]);
>>> false
}
f("foobar");
arguments
Besides numerical indexed access to all provided parameters as well as a length
property for array like access, the arguments
object provides further functionality.
A property callee
is defined on every arguments
object, which provides a reference to the called function itself:
function f() {
alert(arguments.callee === f);
>>> true
}
f();
Even though usually this property is not needed by most applications, there are certain use cases for it, like automatic calling of parent methods inside a prototypical "class based" structure as well as referencing anonymous functions recursively.
Utilizing this feature has however certain major drawbacks as well. First of all, it is possible to use callee
in conjunction with the caller
property on functions to traverse the callstack. This allows JavaScript functions to escape from their environment. Especially in situations where user-generated JavaScript code is allowed, this can lead to security issues.
The second even more important issue with the callee
property is related to optimization. It is impossible for any optimizer to properly inline any function using callee
for performance reasons, as the engine has to be able to provide a clean reference to the original function context. In worst case scenarios, this may slow down execution speed quite dramatically in relevant situations.
In ES5 Strict Mode the usage of callee
as well as caller
is therefore prohibited.
As you have seen, the arguments
object can be a very useful companion in JavaScript. It is indeed the only way of handling functions with an arbitrary parameter count.
Unfortunately, this object is not what you would think it is (an array). Furthermore, it has some surprises in store for you, which you might not encounter anywhere else inside the language (referencing behavior).
Nevertheless, once knowing all those nifty little secrets about the arguments
object, it can be used safely as well as productively. Don't be mad at JavaScript for all of the confusion. The language was created in a haste. The changes within Strict Mode clearly show that there are efforts being made to fix up the language. The future of JavaScript might not be as dark as you think.
Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.