Item 23: Never Modify the arguments Object
Item 23: Never Modify the arguments Object
The arguments object may look like an array, but sadly it does not
always behave like one. Programmers familiar with Perl and UNIX
shell scripting are accustomed to the technique of “shifting” elements
off of the beginning of an array of arguments. And JavaScript’s arrays
do in fact contain a shift method, which removes the first element of
an array and shifts all the subsequent elements over by one. But the
arguments object itself is not an instance of the standard Array type,
so we cannot directly call arguments.shift() .
Thanks to the call method, you might expect to be able to extract the
shift method from an array and call it on the arguments object. This
might seem like a reasonable way to implement a function such as
callMethod , which takes an object and a method name and attempts
to call the object’s method on all the remaining arguments:
function callMethod(obj, method) {
var shift = [].shift;
shift.call(arguments);
shift.call(arguments);
return obj[method].apply(obj, arguments);
}
But this function does not behave even remotely as expected:
var obj = {
add: function(x, y) { return x + y; }
};
callMethod(obj, "add", 17, 25);
// error: cannot read property "apply" of undefined
The reason why this fails is that the arguments object is not a copy
of the function’s arguments. In particular, all named arguments are
aliases to their corresponding indices in the arguments object. So obj
continues to be an alias for arguments[0] and method for arguments[1] ,
even after we remove elements from the arguments object via shift .
This means that while we appear to be extracting obj["add"] , we are
actually extracting 17[25] ! At this point, everything begins to go hay-
wire: Thanks to the automatic coercion rules of JavaScript, this pro-
motes 17 to a Number object, extracts its "25" property (which does
not exist), produces undefined , and then unsuccessfully attempts to
extract the "apply" property of undefined to call it as a method.
The moral of this story is that the relationship between the arguments
object and the named parameters of a function is extremely brittle.
Modifying arguments runs the risk of turning the named parameters
of a function into gibberish. The situation is complicated even further
by ES5’s strict mode. Function parameters in strict mode do not alias
their arguments object. We can demonstrate the difference by writing
a function that updates an element of arguments :
function strict(x) {
"use strict";
arguments[0] = "modified";
return x === arguments[0];
}
function nonstrict(x) {
arguments[0] = "modified";
return x === arguments[0];
}
strict("unmodified"); // false
nonstrict("unmodified"); // true
As a consequence, it is much safer never to modify the arguments
object. This is easy enough to avoid by first copying its elements to a
real array. A simple idiom for implementing the copy is:
var args = [].slice.call(arguments);
The slice method of arrays makes a copy of an array when called
without additional arguments, and its result is a true instance of the
standard Array type. The result is guaranteed not to alias anything,
and has all the normal Array methods available to it directly.
We can fix the callMethod implementation by copying arguments , and
since we only need the elements after obj and method , we can pass a
starting index of 2 to slice :
function callMethod(obj, method) {
var args = [].slice.call(arguments, 2);
return obj[method].apply(obj, args);
}
At last, callMethod works as expected:
var obj = {
add: function(x, y) { return x + y; }
};
callMethod(obj, "add", 17, 25); // 42
Things to Remember
✦ Never modify the arguments object.
✦ Copy the arguments object to a real array using [].slice.call(arguments)
before modifying it.
来源:Effective Javascript