admin管理员组文章数量:1277317
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?
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?
Share Improve this question asked Aug 13, 2012 at 18:00 Peter OlsonPeter Olson 143k49 gold badges208 silver badges249 bronze badges 13-
Why would use use eval in the first place? There is a better solution than
eval
99.9999% of the time. – epascarello Commented Aug 13, 2012 at 18:06 - Undefined behaviour (dunno if the term is used much in the context of JS) – jli Commented Aug 13, 2012 at 18:06
-
@epascarello: This question isn't about using
eval
, it's about the strange behavior that it causes in this case. – voithos Commented Aug 13, 2012 at 18:07 -
4
@epascarello: This has nothing to do with the functional uses of
eval()
. The fact of the matter is that the presence ofeval()
is causing unintended side effects, and the OP wants to know why. – Cᴏʀʏ Commented Aug 13, 2012 at 18:08 - 3 @jli I've read the spec before, and I don't recall any case like this being mentioned as undefined behavior. I can't imagine how this behavior is even defensible--unexecuted statements should never have any effect. – Peter Olson Commented Aug 13, 2012 at 18:10
3 Answers
Reset to default 8 +50TL;DR
The likely cause is the interaction of the non-standard
function.arguments
with browser optimizations for function code containingeval
and/orarguments
. However, only people familiar with the implementation details on each browser would be able to explain the why in depth.
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 arguments
object, 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 patibility. 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 patibility between browsers*, which does not appear to be implemented consistently by vendors (no browser pass all tests). Also, using arguments
as 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 e eval
and arguments
. As you are aware, the ECMAScript specification requires some extra operations to be performed so those language constructs can be used (in the case of eval
, 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 if eval
or arguments
are not used. Those optimizations, bined with the use of a non-standard property of the Function
object, 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), arguments
and fn.arguments
interact on IE, Firefox and Chrome. It's not surprising that the results vary on each browser, since we're dealing with the non-standard fn.arguments
.
The first test just checks for strict equality of fn.arguments
and arguments
, and if the presence of eval
affects that in any way. Inevitably, my Chrome tests are contamined by the presence of arguments
, which has an effect on the results, as you said in the question. Here are the results:
| no eval | direct eval call | indirect eval call
-----------------------+-----------+--------------------+---------------------
IE 9.0.8112.16421 | true | true | true
FF 16.0.2 | false | false | false
Chrome 22.0.1229.94 | true | false | true
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 eval
call.
The remaining tests are assignment tests based on functions that look like the following:
function fn(x) {
// Assignment to x, arguments[0] or fn.arguments[0]
console.log(x, arguments[0], fn.arguments[0]);
return; // make sure eval is not actually called
// No eval, eval(""), or (1,eval)("")
}
Below are the results for each tested browser.
Internet Explorer 9.0.8112.16421
| no eval | direct eval call | indirect eval call
-----------------------------+---------------------------+---------------------------+--------------------------
arguments[0] = 'changed'; | changed, changed, changed | changed, changed, changed | changed, changed, changed
x = 'changed'; | changed, changed, changed | changed, changed, changed | changed, changed, changed
fn.arguments[0] = 'changed'; | changed, changed, changed | changed, changed, changed | changed, changed, changed
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.arguments
is always true, x
, arguments[0]
or function.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
| no eval | direct eval call | indirect eval call
-----------------------------+------------------------------+---------------------------+-----------------------------
arguments[0] = 'changed'; | changed, changed, original | changed, changed, changed | changed, changed, original
x = 'changed'; | changed, changed, original | changed, changed, changed | changed, changed, original
fn.arguments[0] = 'changed'; | original, original, original | changed, changed, changed | original, original, original
Firefox 16.0.2 is less consistent: although arguments
is never === fn.arguments
on Firefox, eval
has an effect on the assignments. Without a direct call to eval
, changing arguments[0]
changes x
too, but does not change fn.arguments[0]
. Changing fn.arguments[0]
does not change either x
or arguments[0]
. It was a plete surprise that changing fn.arguments[0]
does not change itself!
When eval("")
is introduced, the behavior is different: changing one of x
, arguments[0]
or function.arguments[0]
starts affecting the other two. So it's like arguments
bees === function.arguments
– except that it does not, Firefox still says that arguments === function.arguments
is false
. When an indirect eval
call is used instead, Firefox behaves as if there were no eval
.
Chrome 22.0.1229.94
| no eval | direct eval call | indirect eval call
-----------------------------+----------------------------+------------------------------+--------------------------
arguments[0] = 'changed'; | changed, changed, changed | changed, changed, original | changed, changed, changed
x = 'changed'; | changed, changed, changed | changed, changed, original | changed, changed, changed
fn.arguments[0] = 'changed'; | changed, changed, changed | original, original, original | changed, changed, changed
Chrome's behavior is similar to Firefox's: when there is no eval
or an indirect eval
call, it behaves consistently. With the direct eval
call, the link between arguments
and fn.arguments
seem to break (which makes sense, considering arguments === fn.arguments
is false
when eval("")
is present). Chrome also presents the weird case of fn.arguments[0]
being original
even after assignment, but it happens when eval("")
is present (while on Firefox it happens when there's no eval
, 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.
function t1(x) {
console.log("no eval: ", arguments === t1.arguments);
}
function t2(x) {
console.log("direct eval call: ", arguments === t2.arguments);
return;
eval("");
}
function t3(x) {
console.log("indirect eval call: ", arguments === t3.arguments);
return;
(1, eval)("");
}
// ------------
function t4(x) {
arguments[0] = 'changed';
console.log(x, arguments[0], t4.arguments[0]);
}
function t5(x) {
x = 'changed';
console.log(x, arguments[0], t5.arguments[0]);
}
function t6(x) {
t6.arguments[0] = 'changed';
console.log(x, arguments[0], t6.arguments[0]);
}
// ------------
function t7(x) {
arguments[0] = 'changed';
console.log(x, arguments[0], t7.arguments[0]);
return;
eval("");
}
function t8(x) {
x = 'changed';
console.log(x, arguments[0], t8.arguments[0]);
return;
eval("");
}
function t9(x) {
t9.arguments[0] = 'changed';
console.log(x, arguments[0], t9.arguments[0]);
return;
eval("");
}
// ------------
function t10(x) {
arguments[0] = 'changed';
console.log(x, arguments[0], t10.arguments[0]);
return;
(1, eval)("");
}
function t11(x) {
x = 'changed';
console.log(x, arguments[0], t11.arguments[0]);
return;
(1, eval)("");
}
function t12(x) {
t12.arguments[0] = 'changed';
console.log(x, arguments[0], t12.arguments[0]);
return;
(1, eval)("");
}
// ------------
console.log("--------------");
console.log("Equality tests");
console.log("--------------");
t1('original');
t2('original');
t3('original');
console.log("----------------");
console.log("Assignment tests");
console.log("----------------");
console.log('no eval');
t4('original');
t5('original');
t6('original');
console.log('direct call to eval');
t7('original');
t8('original');
t9('original');
console.log('indirect call to eval');
t10('original');
t11('original');
t12('original');
Just playing around, I found that you remove the f.
from the f.arguments
value in the array, and just use arguments
, the behavior is the same no matter what es after the return
:
function f(x) {
return [x, change(arguments), x];
}
function g(x) {
return [x, change(arguments), x];
eval("");
}
function h(x) {
return [x, change(arguments), x];
arguments;
}
In all three cases using x = "original"
, the output is:
["original", " => ", "changed"]
["original", " => ", "changed"]
["original", " => ", "changed"]
In this case, values are modified by change()
as if the arguments
array is passed by reference. To keep "original" unchanged, I might suggest converting the arguments
object to an actual array first (thus passing arguments
' elements by value):
function h(x) {
var argsByValue = Array.prototype.slice.call(arguments, 0);
return [x, change(argsByValue), x];
}
In the above example, x
will remain "original" before and after change()
, because a copy of x
was modified, not the original.
I'm still not sure what the effects of having eval("");
or arguments;
are, but your question is still interesting, as are the results.
What's really strange is that this even affects putting the change()
in its own function scope with a copy of the function arguments
function f(x) {
return ((function(args) {
return [x, change(args), x];
})(f.arguments));
// the presence of the line below still alters the behavior
arguments;
}
It seems that a reference to f.arguments
still holds in this case. Weird stuff.
UPDATES
From MDN:
The
arguments
object is a local variable available within all functions;arguments
as a property ofFunction
can no longer be used.
Seems like, at least for Firefox, you shouldn't be using arguments
as a property (e.g. function foo() { var bar = foo.arguments; }
), though they don't say why.
Here's some excellent Javascript nuances ing into effect:
change(f.arguments)
change(x)
The former passes the argument list into change() as a reference. Arrays tend to be references in Javascript. This means that if you change an element of an array somewhere else, those changes will be applied wherever you use that same array.
The latter passes the argument x as a value. It's like handing off a copy - change can change it around and it will only affect the local variable. Because x is a string, and strings are immutable, args[0] = "changed" in the change() function doesn't do anything. Try the following in a console:
var x = "asdf";
x[0] = "foo";
console.log(x); // should print "asdf"
In the f, h, g functions, the value of arguments[0] is changed in the second index in the returned list. The third index will return "changed".
Theoretically. However, some browsers pile Javascript, which causes kind-of race conditions and instructions may not execute in the order you type them, especially if they are on the same line and you're changing the stack and accessing it from the same line.
return [x, change(f.arguments), x];
...attempts to change the arguments variable and access x (which is an argument) at the same time. In Chrome, for instance, passing f.arguments to change() results in ["original", " => ", "original"] while passing just arguments results in ["original", " => ", "changed"]. It may also be a scoping issue and how Javascript handles value and reference types, but that behaviour is different across browsers.
I didn't see any odd behaviour with eval() given what I've described, but it seems that stating arguments in the h() function after the return creates a side-effect that I suspect is caused by Chrome's pilation of Javascript. What's really interesting is that internally, the shell executes a variable by returning its value, but it's not being written anywhere, expect perhaps to a cache. Hard to tell what's going on in Javascript's stack, but what you're doing is certainly unconventional and it will for sure mess up the piler across browsers.
EDIT:
Even better: console.log(h.arguments); return [x, change(arguments), x]; arguments
will log
["changed"]
["original", " => ", "changed"]
Sure looks like a race condition, or some wonky passing of references to the arguments array within functions!
本文标签: javascriptWhy does an unexecuted eval have an effect on behavior in some browsersStack Overflow
版权声明:本文标题:javascript - Why does an unexecuted eval have an effect on behavior in some browsers? - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1741276836a2369777.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论