UPDATE: The point of this post is to raise awareness that reassigning the value of an argument variable mutates the arguments
object. The code example is contrived and exists solely to help illustrate that behavior.
Did you know that a JavaScript function’s named parameter variables are synonyms for the corresponding elements in that function’s Arguments object?
I ran into this while experimenting with a function that was written to take either two or three arguments, providing a default for the first argument if only two are passed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var makePerson = function(favoriteColor, name, age) { if (arguments.length < 3) { favoriteColor = "green"; name = arguments[0]; age = arguments[1]; } return { name: name, age: age, favoriteColor: favoriteColor }; }; var person = makePerson("Joe", 18); console.log(JSON.stringify(person)); // => {"name":"green","age":"green","favoriteColor":"green"} |
Strangely, all of the values in the result object are set to "green"
. I was expecting to see
|
// => {"name":"Joe","age":18,"favoriteColor":"green"}
|
But when I set favoriteColor
to "green"
I was also changing arguments[0]
to be "green"
. The situation only got worse when I set name = arguments[0]
effectively changing arguments[1]
to be "green"
as well.
I had not realized that named arguments are synonyms for the elements of the Arguments object. I found a good explanation on Rx4AJAX:
The numbered properties of the Arguments Object are synonymous with the local variables that hold the named function parameters. They both reference the same address in the stack. If the function body has code that changes the value of a parameter either via a name reference or the arguments[] array reference, both referenced values will reflect the same value.
Regardless of the language, it is generally not a good idea to reassign the parameter variables inside a function. In JavaScript it turns out to be a really bad idea.
Additional information:
- Check out this jsFiddle to experiment with the code snippet above.
- A comment on this StackOverflow question describes this “magical behavior” of JavaScript.
- JavaScript Garden describes this behavior in its section on the arguments object.
So the arguments object isn’t a “real” array, so you run into problems when you treat it as such.
Here’s a working example where you turn the arguments object into an array with Array.prototype.slice()
http://jsfiddle.net/wookiehangover/yZPj8/4/
This is a pretty common beginner’s mistake and is covered in most advanced books, such as Javascript Patterns or High Performance Javascript.
Here’s a good resource about how the arguments object works: https://developer.mozilla.org/en/JavaScript/Reference/functions_and_function_scope/arguments
If you slice the Arguments, the get aan array, which is not “live”. This way, you can reassign the arguments without any problems.
http://jsfiddle.net/Avorin/yZPj8/6/
When I want to pass a varying number of parameters to a function, I either use a predefined object or an object literal myself to begin with (I presume this example function is simplified).
You can also clutter up the function calls with things like makePerson(null, “Joe”, 18) and test for nulls, too, instead of array lengths.
This is the solution I found, using this. instead of an args array. I’m not sure which solution is better.
http://jsfiddle.net/Q2LMT/
Or simply refer to the arguments by name when changing their values.
This article roughly says:
When you misuse the arguments object, unexpected results happen.
The solution: don’t misuse the arguments object. Leave the param list empty and use your logic to fill out variable names if you need that
This is why I love working with Rails… most Rails functions take hashes as arguments, so you can your real arguments in in any order, and it guarantees code verbosity. Example:
button_to ‘Add to Cart’, line_items_path(:product_id => product), :remote => true
where :remote=>true is the third argument, a hash, and contains all optional parameters you could add (in this case, :method, :disabled, :confirm, and :remote).
var makePerson = function(favoriteColor, name, age) {
if (arguments.length < 3) { favoriteColor = "green"; name = arguments[0]; age = arguments[1]; } return { name: name, age: age, favoriteColor: (arguments.length < 3 ? "green" : favoriteColor) }; };
How very Perl-ish of Javascript.
Ignore this blog post’s advice. It is perfectly fine to reassign function arguments in Javascript. If you just follow the convention of putting option arguments at the end of the argument list instead of the beginning, you avoid this problem all together and simplify your code:
var makePerson = function(name, age, favoriteColor) {
favoriteColor = favoriteColor || “green”;
return { name: name, age: age, favoriteColor: favoriteColor };
};
Who makes the first argument optional? Seriously? There are numerous things wrong with your code.
What a terrible programming language.
Larry Clapp, this isn’t perlish at all. In Perl you do named parameters through local variables. They’re duplicated not ref-copied.
use strict;
use warnings;
my $makePerson = sub {
my ( $favoriteColor, $name, $age ) = @_;
if ( @_ < 3 ) { $favoriteColor = "green"; $name = $_[0]; $age = $_[1]; } return { name => $name
, age => $age
, favoriteColor => $favoriteColor
}
};
use Data::Dumper;
die Dumper $makePerson->(‘Joe’, 18);
What you’re confusing is Perl’s special array variable `@_` which is used to store references to the parameters from the caller, making them accessible in the callee. So the sub implementation themselves are pass-by-reference, but the assignment itself requires a total copy. Not to say you couldn’t achieve the same effect with Perl if you *really wanted too*, but it requires a ton of non-accidental (contrived) work.
use strict;
use warnings;
my $makePerson = sub {
my ( $favoriteColor, $name, $age ) = ( \$_[0], \$_[1], \$_[2] );
#my ( $favoriteColor, $name, $age ) = @_;
if ( length @_ < 3 ) { $$favoriteColor = "green"; $$name = $_[0]; $$age = $_[1]; } return { name => $$name
, age => $$age
, favoriteColor => $$favoriteColor
}
};
use Data::Dumper;
my $name = ‘Joe’;
my $age = 18;
die Dumper $makePerson->($name, $age);
How about just using a configuration object?
var person = makePerson({name:”Joe”, age:18})
Inside the function look for the property you want to default.
JavaScript reveals more and more of its awful design. NaN != NaN ?????
the problem isn’t with using
arguments
, the problem is with your use of it.Writing the code:
function example (x, y, z) {
x = 1;
y = arguments[0];
z = arguments[1];
}
will make every value 1 because I wasn’t very careful about the order of my actions.
As the article you quoted states, the variables x, y, and z are synonymous with arguments [0], [1], and [2] respectively, so if I called
example(3,4)
all I would be doing in my function is assigning 3 to x and 4 to y with the function call, then assigning 1 to x, the value of x to y, then the value of y to z. All of my values would be the same (and 1) afterwards.You do the same thing in your code. You pass in
(favoriteColor: Joe, name: 18)
and then set the favoriteColor to “green” before taking the value of “green” and pasting it on to the name, then taking the new value of name (also “green”) and pasting it in to the value of age. If you had instead written that code in the reverse order, it would have worked as you had initially expected.[…] service allows you to react immediately to spikes in website traffic. Just recently our blog had a post go viral on Reddit causing an extreme spike in traffic. Using a live information radiator on our office […]
There are special edge cases like Array.prototype.reduce where assign to accumulator argument passed between iterations is only good practice:
const aggregate = someArray.reduce((acc, item) => {
acc[item.prop] = (acc[item.prop] || 0) + 1;
}, {} /* this is initial state */);
Choose Parameters or Arguments….but using both is asking for trouble.
If you are using Parameters defined in the Function signature, then you have no need to refer to the arguments information.
If you plan on using arguments, then do not define Parameters.
Mixing the two, is asking for problems and the reason for the overall purpose of this post.