TL;博士
可能的原因是非标准function.arguments
与包含eval
和/或的函数代码的浏览器优化的交互arguments
。但是,只有熟悉每个浏览器的实现细节的人才能深入解释其中的原因。
这里的主要问题似乎是使用非标准Function.prototype.arguments
. 当你不使用它时,奇怪的行为就会消失。
规范只提到了arguments
object,并没有说它可以被视为一个属性,前缀为[funcName].
. 我不确定它来自哪里,但它可能是 ES3 之前的东西,为了向后兼容而保留在浏览器上。正如 Cory 的回答所说,现在不鼓励在 MDN 上使用这种方法。然而,MSDN并没有对此表示任何反对。我还发现本规范中提到了浏览器之间的兼容性*,供应商似乎并没有一致地实现它(没有浏览器通过所有测试)。此外,arguments
在严格模式下不允许用作函数的属性(同样,这不在 ECMA 规范中,IE9 似乎忽略了限制)。
然后来eval
和arguments
。如您所知,ECMAScript 规范需要执行一些额外的 操作,以便可以使用这些语言结构(在 的情况下,操作因调用是否直接eval
而异)。由于这些操作可能会对性能产生影响,因此(有些?)JavaScript 引擎会执行优化以避免使用或不使用它们。这些优化,加上对象的非标准属性的使用,似乎是导致你得到奇怪结果的原因。不幸的是,我不知道每个浏览器的实现细节,所以我不能给你一个确切的答案为什么eval
arguments
Function
我们看到了这些附带影响。
(*)顺便说一下,由SO 用户编写的规范。
测试
我进行了一些测试以了解eval
(直接和间接调用)如何在 IE、Firefoxarguments
和fn.arguments
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)("")
}
以下是每个测试浏览器的结果。
互联网浏览器 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是最一致的浏览器。正如在 IEarguments === fn.arguments
上始终为真,x
或arguments[0]
都function.arguments[0]
指向相同的值。如果您更改其中任何一个,所有三个都将输出相同的更改值。
火狐 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
从未在 === fn.arguments
Firefox 上,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
.
铬 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
存在)。Chrome 还呈现了即使在分配之后仍然存在的奇怪情况,但它会在存在时发生(而在 Firefox 上,它会在没有或间接调用时发生)。false
eval("")
fn.arguments[0]
original
eval("")
eval
这是测试的完整代码,如果有人想运行它们。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');