我们假设我有这两个功能:
function change(args) {
args[0] = "changed";
return " => ";
}
function f(x) {
return [x, change(f.arguments), x];
}
console.log(f("original"));
在除Opera之外的大多数浏览器中,都会返回["original", " => ", "original"]
。
但如果我像这样更改f
函数,
function f(x) {
return [x, change(f.arguments), x];
eval("");
}
它将在IE9,Safari 5和Firefox 16和17中返回["original", " => ", "changed"]
。
如果我将eval("")
替换为arguments
,则Chrome也会更改。
你可以test it on your own browser on jsFiddle.
我根本不明白这种行为。如果函数在执行这些语句之前返回,那么这些语句如何影响返回值?即使这些陈述被执行,为什么它们会对参数变异产生任何影响呢?
答案 0 :(得分:9)
TL; DR
可能的原因是非标准
function.arguments
与包含eval
和/或arguments
的功能代码的浏览器优化的互动。但是,只有熟悉每个浏览器实现细节的人才能够深入解释为什么。
这里的主要问题似乎是使用非标准Function.prototype.arguments
。当你不使用它时,奇怪的行为就会消失。
规范仅提及arguments
object,并且从不说它可能被视为属性,前缀为[funcName].
。我不知道它来自哪里,但它可能是ES3之前的东西,为了向后兼容而保留在浏览器上。正如Cory的回答所述,使用的是now discouraged on MDN。但是,MSDN并没有反对它。我还发现它在specification for compatibility between browsers * 上提到,供应商似乎没有一致地实现(没有浏览器通过所有tests)。此外,在严格模式下不允许使用arguments
作为函数的属性(同样,不在ECMA规范中,IE9似乎忽略了限制)。
然后来eval
和arguments
。 As you are aware,ECMAScript规范要求执行一些extra operations,以便可以使用这些语言结构(在eval
的情况下,操作因呼叫而异是direct还是没有。由于这些操作会对性能产生影响,(某些?)JavaScript引擎会执行优化,以避免在未使用eval
或arguments
的情况下进行优化。这些优化与Function
对象的非标准属性的使用相结合,似乎是导致您获得奇怪结果的原因。很遗憾,我不知道每个浏览器的实施细节,因此我无法就为什么我们看到这些附带效果给出准确的答案。
(*)顺便写一个SO user的规范。
我运行了一些测试,以了解eval
(直接和间接呼叫),arguments
和fn.arguments
如何在IE,Firefox和Chrome上进行互动。由于我们处理的是非标准fn.arguments
,所以每个浏览器的结果都有所不同并不奇怪。
第一个测试只是检查fn.arguments
和arguments
的严格平等,以及eval
是否以任何方式影响它。正如你在问题中所说的那样,我的Chrome测试不可避免地受到arguments
的影响,这会对结果产生影响。结果如下:
| 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
你可以看到IE和Firefox更加一致:IE上的对象总是相同的,在Firefox上永远不会相同。但是,在Chrome中,如果功能代码不包含直接eval
调用,则它们仅相等。
其余测试是基于如下所示功能的分配测试:
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)("")
}
以下是每个测试浏览器的结果。
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
首先,我的IE测试似乎给出的结果与问题中的结果不同;我总是得到改变"在IE上。也许我们使用不同的IE构建?无论如何,上面显示的结果是IE是最一致的浏览器。由于IE arguments === fn.arguments
始终为真,x
,arguments[0]
或function.arguments[0]
都指向相同的值。如果您更改其中任何一个,则所有三个都将输出相同的更改值。
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不太一致:虽然arguments
在Firefox上从不 === fn.arguments
,但eval
会对分配产生影响。如果没有直接调用eval
,则更改arguments[0]
也会更改x
,但不会更改fn.arguments[0]
。更改fn.arguments[0]
不会更改x
或arguments[0]
。令人惊讶的是,改变fn.arguments[0]
并没有改变自己!
引入eval("")
时,行为会有所不同:更改x
,arguments[0]
或function.arguments[0]
中的一个会影响其他两个。因此arguments
变为=== function.arguments
- 除了它没有,Firefox仍然说arguments === function.arguments
是false
。当使用间接eval
调用时,Firefox的行为就像没有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的行为类似于Firefox:当没有eval
或间接eval
调用时,它的行为一致。通过直接eval
调用,arguments
和fn.arguments
之间的链接似乎已中断(这有意义,arguments === fn.arguments
false
eval("")
存在)。即使在分配后,Chrome也会出现fn.arguments[0]
original
eval("")
的奇怪情况,但是当eval
出现时会发生这种情况(而在Firefox上,当没有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');
时就会发生这种情况。 },或间接调用。)
以下是测试的完整代码,如果有人想运行它们。还有live version on jsfiddle。
{{1}}
答案 1 :(得分:3)
只是玩游戏,我发现您从数组中的f.
值中删除f.arguments
,并且只使用arguments
,无论后面发生什么,行为都是一样的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;
}
在使用x = "original"
的所有三种情况下,输出为:
["original", " => ", "changed"]
["original", " => ", "changed"]
["original", " => ", "changed"]
在这种情况下,值由change()
修改,就好像arguments
数组通过引用传递一样。为了保持“原始”不变,我可能会建议先将arguments
对象转换为实际数组(从而按值传递arguments
'元素 ) :
function h(x) {
var argsByValue = Array.prototype.slice.call(arguments, 0);
return [x, change(argsByValue), x];
}
在上面的示例中,x
将在change()
之前和之后保持“原始”状态,因为x
的副本已被修改,而不是原始版本。
我仍然不确定eval("");
或arguments;
的影响是什么,但您的问题仍然很有趣,结果也是如此。
真正奇怪的是,这甚至影响将change()
置于其自己的函数范围内,并附带函数参数的副本
function f(x) {
return ((function(args) {
return [x, change(args), x];
})(f.arguments));
// the presence of the line below still alters the behavior
arguments;
}
在这种情况下,f.arguments
似乎仍然存在引用。奇怪的东西。
<强>更新强>
来自MDN:
arguments
对象是所有函数中可用的局部变量;arguments
作为Function
的属性已无法再使用。
似乎,至少对于Firefox,你不应该使用arguments
作为属性(例如function foo() { var bar = foo.arguments; }
),尽管他们没有说明原因。
答案 2 :(得分:1)
以下是一些出色的Javascript细微差别:
change(f.arguments)
change(x)
前者将参数列表传递给change()作为参考。数组往往是Javascript中的引用。这意味着如果您在其他位置更改数组的元素,则无论您在何处使用相同的数组,都会应用这些更改。
后者将参数x 作为值传递。就像传递副本一样 - 更改可以改变它,它只会影响局部变量。因为x是一个字符串,并且字符串是不可变的,所以change()函数中的args [0] =“changed”不会做任何事情。在控制台中尝试以下操作:
var x = "asdf";
x[0] = "foo";
console.log(x); // should print "asdf"
在f,h,g函数中,参数[0]的值在返回列表的第二个索引中更改。第三个指数将返回“已更改”。
理论上。但是,有些浏览器会编译Javascript,导致种类条件和指令可能无法按您键入的顺序执行,特别是如果它们位于同一行并且您正在更改堆栈并从同一行访问它。 / p>
return [x, change(f.arguments), x];
...尝试同时更改参数变量并访问x(这是一个参数)。例如,在Chrome中,将f.arguments传递给change()会导致[“original”,“=&gt;”,“original”],而只传递参数会导致[“original”,“=&gt;”,“更改“。它也可能是一个范围问题以及Javascript如何处理值和引用类型,但这种行为在浏览器中是不同的。
根据我所描述的内容,我没有看到eval()的任何奇怪行为,但似乎在返回后在h()函数中声明参数会产生副作用,我怀疑这是由Chrome的编译引起的的Javascript。真正有趣的是,在内部,shell通过返回其值来执行变量,但它不会被写入任何地方,可能期望缓存。很难说Javascript的堆栈中发生了什么,但你正在做的事情肯定是非常规的,它肯定会在浏览器中搞乱编译器。
编辑:
更好: 的console.log(h.arguments); return [x,change(arguments),x]; 参数
将记录
["changed"]
["original", " => ", "changed"]
当然看起来像竞争条件,或者在函数中对参数数组的引用有些不稳定!