Formal parameters and the arguments object in JavaScript

24 07 2011

Say you’re writing a function which takes a function name as an argument and passes the rest of its arguments to the named function. Your unit test for such a function may look like this:

assertEquals('add', 5, calculate('add', 3, 2));
assertEquals('mul', 6, calculate('mul', 3, 2));

We would like to splice off the first element and pass the rest to another function but since arguments isn’t a real Array it doesn’t support the splice method. Never mind, that’s easily treated by calling splice with arguments as this.

function calculate(operation)
{
    // Remove `operation' from arguments leaving only `a' and `b'
    Array.prototype.splice.call(arguments, 0, 1);
    var funcs = {
        add: function(a, b) { return a + b; },
        mul: function(a, b) { return a * b; }
    }
    return funcs[operation].apply(this, arguments);
}

So why are we getting an Uncaught TypeError: Cannot call method ‘apply’ of undefined? Looking at things in Chrome’s debugger everything looks OK. operation is “add” and funcs[operation] is a function.

View in Chrome developer tools
It dawned on me that the debugger is lying (Firebug and IE’s debugger don’t have this problem), splice removed the first parameter from arguments and now the value of operation is 3. How come 3? Well operation was the first parameter and the first element in arguments is now 3, it’s obvious really(?).

Now I picked up JavaScript on my own (as I assume many do) so I’m no expert and this took me by surprise (it’s probably not trivial even for real JavaScript developers if Chrome’s debugger has this defect).

After meditating about the facts it makes sense that formal parameters and the elements in the arguments object are aliases so that if you do arguments[2]++ the formal parameter  ‘b’ changes too but this behaviour means that that these references are more like Perl’s much vilified symbolic references than most languages’ references. This means that using a formal parameter in JavaScript is semantically equivalent to indexing into the arguments object, writing  operation in (this example) is just syntactic sugar for arguments[0]. If you want to remove an argument from the arguments object and still used the named parameter you must first cache the value in a separate variable.

Here’s the fixed version of calculate:

function calculate(operation)
{
     var op = operation; // Save value of `operation'
    // Remove `operation' from arguments leaving only `a' and `b'
    Array.prototype.splice.call(arguments, 0, 1);
    var funcs = {
        add: function(a, b) { return a + b; },
        mul: function(a, b) { return a * b; }
    }
    return funcs[op].apply(this, arguments);
}

Edit: as strager said in the reddit thread:

Takeaway:
Do not modify the arguments object.
Ever.

Now that I’m older and slightly less ignorant I think this would be a better solution:

function calculate(operation)
{
    var funcs = {
        add: function(a, b) { return a + b; },
        mul: function(a, b) { return a * b; }
    }
    return funcs[operation].apply(this,
                       Array.prototype.slice.call(arguments, 1));
}

Edit January 2012: The Chrome defect has been fixed (verified on Chrome 16).

Advertisements

Actions

Information

4 responses

26 02 2015
The World of Javascript - Tech NexusTech Nexus

[…] Exchange: Perhaps the most important feature of Javascript is the acceptance of the formal parameters. Although innumerable amount of data and parameters can be achieved, but via the application of […]

22 06 2015
JP Erasmus

I could be wrong, but should “splice” not be “slice” without the “p”?

22 06 2015
Motti Lanzkron

Indeed using slice is better (as I did in the end), I originally used splice since I wanted to mutate the arguments object.

See also:
http://ariya.ofilabs.com/2014/02/javascript-array-slice-vs-splice.html

23 06 2015
JP Erasmus

Ah thanks. I also found that article after I made the comment on your blog. Learnt something new. Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




%d bloggers like this: