Let’s suppose I have these two functions:
function change(args) {
args[0] = "changed";
return " => ";
}
function f(x) {
return [x, change(f.arguments), x];
}
console.log(f("original"));
In most browsers, except Opera, this returns ["original", " => ", "original"].
But if I change the f function like this,
function f(x) {
return [x, change(f.arguments), x];
eval("");
}
it will return ["original", " => ", "changed"] in IE9, Safari 5, and Firefox 16 and 17.
If I replace eval("") with arguments, it will also change in Chrome.
You can test it on your own browser on jsFiddle.
I don’t understand this behavior at all. If the function returns before those statements are executed, how can those statements affect the return value? Even if the statements were executed, why would they have any effect on argument mutation?
The main problem here appears to be the use of the non-standard
Function.prototype.arguments. When you don’t use it, the strange behavior goes away.The specification only mentions the
argumentsobject, and never says it might be treated as a property, prefixed with[funcName].. I’m not sure where that came from, but it’s probably something pre-ES3, kept on browsers for backward compatibility. As Cory’s answer states, that use is now discouraged on MDN. MSDN, however, doesn’t say anything against it. I also found it mentioned on this specification for compatibility between browsers*, which does not appear to be implemented consistently by vendors (no browser pass all tests). Also, usingargumentsas a property of the function is not allowed in strict mode (again, that’s not in the ECMA specs, and IE9 seems to ignore the restriction).Then come
evalandarguments. As you are aware, the ECMAScript specification requires some extra operations to be performed so those language constructs can be used (in the case ofeval, the operation is different depending on the call being direct or not). Since those operations can have an impact on performance, (some?) JavaScript engines perform optimizations to avoid them ifevalorargumentsare not used. Those optimizations, combined with the use of a non-standard property of theFunctionobject, seem to be what’s causing the strange results you got. Unfortunately, I don’t know the implementation details for each browser, so I can’t give you a precise answer on why we see those collateral effects.(*) Spec written by an SO user, by the way.
Tests
I ran some tests to see how
eval(direct and indirect calls),argumentsandfn.argumentsinteract on IE, Firefox and Chrome. It’s not surprising that the results vary on each browser, since we’re dealing with the non-standardfn.arguments.The first test just checks for strict equality of
fn.argumentsandarguments, and if the presence ofevalaffects that in any way. Inevitably, my Chrome tests are contamined by the presence ofarguments, which has an effect on the results, as you said in the question. Here are the results:You can see IE and Firefox are more consistent: the objects are always equal on IE, and never equal on Firefox. In Chrome, however, they’re only equal if the function code does not contain a direct
evalcall.The remaining tests are assignment tests based on functions that look like the following:
Below are the results for each tested browser.
Internet Explorer 9.0.8112.16421
First of all, it seems my IE tests give different results than what is stated in the question; I always get "changed" on IE. Maybe we used different IE builds? Anyway, what the results above show is that IE is the most consistent browser. As on IE
arguments === fn.argumentsis always true,x,arguments[0]orfunction.arguments[0]all point to the same value. If you change any of them, all three will output the same changed value.Firefox 16.0.2
Firefox 16.0.2 is less consistent: although
argumentsis never=== fn.argumentson Firefox,evalhas an effect on the assignments. Without a direct call toeval, changingarguments[0]changesxtoo, but does not changefn.arguments[0]. Changingfn.arguments[0]does not change eitherxorarguments[0]. It was a complete surprise that changingfn.arguments[0]does not change itself!When
eval("")is introduced, the behavior is different: changing one ofx,arguments[0]orfunction.arguments[0]starts affecting the other two. So it’s likeargumentsbecomes=== function.arguments– except that it does not, Firefox still says thatarguments === function.argumentsisfalse. When an indirectevalcall is used instead, Firefox behaves as if there were noeval.Chrome 22.0.1229.94
Chrome’s behavior is similar to Firefox’s: when there is no
evalor an indirectevalcall, it behaves consistently. With the directevalcall, the link betweenargumentsandfn.argumentsseem to break (which makes sense, consideringarguments === fn.argumentsisfalsewheneval("")is present). Chrome also presents the weird case offn.arguments[0]beingoriginaleven after assignment, but it happens wheneval("")is present (while on Firefox it happens when there’s noeval, or with the indirect call).Here is the full code for the tests, if anyone wants to run them. There’s also a live version on jsfiddle.