Mysterious arguments object assignments

This past week, I found what I thought was a bug in Firefox’s JavaScript implementation and filed it. A response from Brendan Eich indicated that the behavior in question was, in fact, compliant with the spec and had been implemented for some time. I ran a few more tests to try and figure out where I had gone wrong. Indeed, Firefox, Internet Explorer, and Safari all exhibited the same behavior while Chrome did not. Here’s what happened.

The code

The code in question is as follows:

function doAdd(num1, num2) {
    if(arguments.length == 1) {
        arguments[1] = 10;
    }
    alert(arguments[0] + num2);
}
doAdd(10);

What would you expect the bottom line to output? In Chrome, it outputs 20, because assigning to arguments[1] also assigns to num2. In the other browsers, it outputs NaN, because assigning to arguments[1] does not also assign to num2. What exactly is going on here?

The spec

My confusion stemmed from Section 10.6 Note 1 of ECMA-262, 5th Edition, which reads:

For non-strict mode functions the array index (defined in 15.4) named data
properties of an arguments object whose numeric name values are less than
the number of formal parameters of the corresponding function object initially
share their values with the corresponding argument bindings in the function’s
execution context. This means that changing the property changes the
corresponding value of the argument binding and vice-versa.

I’ve discussed the similar clause before, at least the 3rd edition one, when answering Baranovskiy’s JavaScript quiz. I thought I had understood that arguments was always bound to the named arguments of the function. Both Tom Schuster and Brendan Eich pointed out that earlier in Section 10.6, in the instructions for creating the arguments object, the following appears:

args the actual arguments passed to the [[Call]] internal method

  1. Let len be the number of elements in args.
  2. Let indx = len – 1.
  3. Repeat while indx >= 0,

So the arguments object is created based on the number of actual arguments passed into the function and not on the number of named parameters defined for the function. That means, as Tom pointed out, the setter that would be created for the numeric index of the arguments object only applies to the number of arguments that were actually passed in. In my example, therefore, arguments[1] becomes a straight property assignment to the arguments object rather than calling the special setter that would copy the value to the named argument.

More code

So even though my previous example wouldn’t work in all browsers, this one will:

function doAdd(num1, num2) {
    arguments[1] = 10;
    alert(arguments[0] + num2);
}
doAdd(10, 25);   //20

The last line of this example outputs 20 in all browsers. Since I’m now passing in two arguments to the function, that means the arguments object is being created with two slots and therefore the special setter works for both indices 0 and 1. Setting arguments[1] in this code actually does update num2 and overwrites the value that was passed in.

Conclusion

Specifications are difficult to understand and even more difficult to implement. This was just a subtle reminder that there are dark corners of JavaScript where dragons lie. It’s fun to poke the dragons from time to time and learn exactly what they’ll do. Sometimes they’ll burn you, but you’ll learn either way.

Comments

  1. abhijeet

    very good point
    i had the impression that arguments array can't be modified

  2. Arlo Carreon

    Seems like everyday you can learn something new about javascript. Thanks for keeping us little guys informed.

  3. Steve Clay

    Lesson: don't rely on argument changes affecting named parameters. Good to know!

  4. Lars Gunther

    Just curious. Have you filed a bug with Chrome, since that implementation is the broken one?

    2nd question. Is this covered in the ECMAScript conformance test suite? Otherwise it probably needs a bug report as well.

  5. Lars Gunther

    Never mind question one. I've found the Chrome bug.

  6. Jays

    An other item on the Chrome issue list is also interesting. It also is about the arguments object (array?)
    an other Chorme bug

  7. matt snider

    Nicholas,

    I am interested in knowing why you would be using the arguments array in this example. It seems like the 'num2' variable would be a better choice. Were you just exploring JavaScript or is there a method behind the madness that I am missing?

    -matt

  8. Nicholas C. Zakas

    @Matt - No, this is purely madness. I generally don't like using the arguments at all, as I think it obscures the meaning of data that's being passed in. This was just a quick demo that I came up with to test what was going on.

Understanding JavaScript Promises E-book Cover

Demystify JavaScript promises with the e-book that explains not just concepts, but also real-world uses of promises.

Download the Free E-book!

The community edition of Understanding JavaScript Promises is a free download that arrives in minutes.