Ответ 1
TL; DR
Вероятная причина - взаимодействие нестандартного
function.arguments
с оптимизацией браузера для кода функции, содержащегоeval
и/илиarguments
. Тем не менее, только люди, знакомые с деталями реализации в каждом браузере, смогут объяснить, почему в глубину.
Основная проблема здесь заключается в использовании нестандартного Function.prototype.arguments
. Когда вы его не используете, странное поведение уходит.
В спецификации упоминается объект arguments
и никогда не говорит, что его можно рассматривать как свойство с префиксом [funcName].
. Я не уверен, откуда это взялось, но, вероятно, что-то пред-ES3, хранится в браузерах для обратной совместимости. Как сказано в ответе Кори, использование теперь обескуражено в MDN. MSDN, однако ничего не говорит против него. Я также нашел в нем упомянутую * которая, как представляется, не выполняется последовательно поставщиков (браузер не пропускает все tests). Кроме того, использование arguments
в качестве свойства функции не допускается в строгом режиме (опять же, это не в спецификациях ECMA, а IE9, похоже, игнорирует ограничение).
Затем наберите eval
и arguments
. Как вам известно, спецификация ECMAScript требует некоторого extra операции, чтобы эти языковые конструкции могли использоваться (в случае eval
операция отличается в зависимости от того, что вызов direct или нет). Поскольку эти операции могут влиять на производительность, некоторые (некоторые?) JavaScript-движки выполняют оптимизации, чтобы избежать их, если eval
или arguments
не используются. Эти оптимизации, в сочетании с использованием нестандартного свойства объекта Function
, по-видимому, вызывают у вас странные результаты. К сожалению, я не знаю детали реализации для каждого браузера, поэтому я не могу дать вам точный ответ о том, почему мы видим эти побочные эффекты.
(*) Spec, написанный пользователем SO, кстати.
Испытания
Я провел несколько тестов, чтобы увидеть, как 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 является самым последовательным браузером. Как и в 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
никогда не === 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
.
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("")
(в то время как в Firefox это происходит, когда нет 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');