Qafoo GmbH - passion for software quality

Help you and your team benefit from new perspectives on cutting-edge quality engineering techniques and tools through the Qafoo team weblog.

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.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

A tricky Thing: The arguments-object

Incited 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.

The arguments-object

Get 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 Behavior

As 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

Where the confusing parts begin

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 array

The 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'
Utilizing array functionality on 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:

  1. Locate the slice function, which is usable on arrays

  2. Execute this function

  3. 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.

References instead of copies

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.

Declared, but not provided arguments

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.

Strict Mode and the arguments object

Unfortunately, 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");

Callstack traversal using 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.

Summing everything up

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.

Download our free e-book "Crafting Quality Software" with a selection of the finest blog posts as PDF or EPub.

Crafting Quality Software

You can also buy a printed version of the book on Amazon or on epubli.

Get Technical Insights With Our Newsletter

Stay up to date with regular new technological insights by subscribing to our newsletter. We will send you articles to improve your developments skills.

Comments