Почему функция, переопределяющая себя, ведет себя по-разному в Chrome/IE и Firefox?

Рассмотрим следующий код:

function f() {
    f = eval("" + f);
    console.log("Inside a call to f(), f is: \n%s", f);
}

f();

console.log("After a call to f(), f is: \n%s", f);

Я ожидал, что f будет определяться во время выполнения. Однако в Chrome и IE это значение undefined, когда вызывается первый console.log, а в Firefox - undefined, когда вызывается второй console.log.

Почему f не всегда определяется? Почему Chrome/IE и Firefox ведут себя по-другому?

http://jsfiddle.net/G2Q2g/

Вывод на Firefox 26:

Внутри вызова функции f() f есть:

function f() {
    f = eval("" + f);
    console.log("Inside a call to f(), f is: \n%s", f);
}

После вызова функции f() f:

undefined

Вывод на Chrome 31 и IE 11:

Внутри вызова функции f() f есть:

undefined

После вызова функции f() f:

function f() {
    f = eval("" + f);
    console.log("Inside a call to f(), f is: \n%s", f);
}

Ответ 1

Прежде всего, расскажите о том, чего мы ожидаем.

Я бы наивно ожидал, что оба случая вернут undefined.

  • Также как: eval("function foo(){}"), который возвращает undefined.

  • Как и всякий раз, когда у нас есть объявление функции, оно не возвращает значение функции, но устанавливает его.

  • Так же, как спецификация langue говорит о строгом режиме.

Обновление: после того, как вы выкапываете больше через spec - Firefox здесь.

Вот что делает Firefox

Визуализация:

  • f = eval("" + f);//установите левую сторону в функцию f, мы находимся в
  • f = eval ("" + f);//объявить новую функцию f в рамках этой функции
  • f = undefined;//поскольку undefined === eval("function(){}"); *

*, поскольку объявления функций ничего не возвращают - точно так же, как функция foo() {} не имеет возвращаемое значение

Так как f было принято на шаге 1, прямо сейчас ссылка на функцию, в которой мы находимся, была перезаписана с помощью undefined, а объявленное локальное закрытие f было объявлено с тем же кодом. Теперь, когда мы делаем:

console.log("Inside a call to f(), f is: \n%s", f )//f - локальная переменная закрытия, ближайшая

Внезапно очевидно, что мы получаем функцию - это переменная-член.

Однако, как только мы выйдем из функции

console.log("After a call to f(), f is: \n%s", е );

Здесь f является undefined, поскольку мы перезаписали его на шаге 1.

Chrome и IE делают ошибку, присваивая ей неправильный f и оценивая правую сторону перед левой стороной задания.

Почему он работает в строгом режиме

Обратите внимание, что в следующем разделе говорится о вводе кода eval:

Пусть strictVarEnv будет результатом вызова NewDeclarativeEnvironment, передающего LexicalEnvironment в качестве аргумента.

Это объясняет, почему он работает в строгом режиме - все это выполняется в новом контексте.


То же самое, но в большем количестве текста и менее графически

  • "Найти" f из f = (так как сначала нужно оценить левую сторону. Это относится к локальной копии f. То есть сначала оценивайте левую сторону.
  • Выполните вызов eval, который возвращает undefined, но объявляет новую локальную функцию f.
  • Так как f из f = оценивался до самой функции, когда мы присваиваем ей undefined, мы фактически заменяем глобальную функцию
  • Итак, когда мы делаем console.log внутри, мы имеем в виду локальную копию, объявленную в eval, поскольку она ближе в цепочке областей видимости.
  • Когда мы находимся снаружи и делаем console.log, теперь мы ссылаемся на "глобальный" f, который мы назначили undefined to.

Фокус в том, что мы назначаем f, а f, который мы регистрируем, - это два разных fs. Это связано с тем, что левая часть задания всегда оценивается сначала (с 11.13.1 в спецификации).

IE и Chrome делают ошибку при назначении локальному f. Это явно неверно, поскольку в спецификации четко сказано:

  • Пусть lref является результатом оценки LeftHandSideExpression.

  • Пусть rref является результатом вычисления AssignmentExpression.

Итак, поскольку мы видим, что сначала необходимо оценить lref.

(ссылка на соответствующий esdiscuss поток)

Ответ 2

Извините, но я могу ответить только на ваш первый вопрос: -/

Я ожидал, что f будет определяться во время выполнения. Почему f не всегда определяется?

Две вещи:

  • eval Объявление функции возвращает undefined. Он может быть переопределен по мере его оценки, но затем после этого назначьте undefined f.
  • f является локальной переменной в функции f, так как именованные функции доступны в своих собственных областях.

Проверьте это поведение в http://jsfiddle.net/G2Q2g/5/.

Итак, вы хотя бы можете спросить

Почему это undefined, когда второй console.log вызывается в Firefox, в отличие от правильного поведения в Opera, Chrome и IE?

Ответ 3

Измените свой код на

function f() {
    f = eval("(" + f + ")");
    console.log("Inside a call to f(), f is: \n%s", f);
};

f();

console.log("After a call to f(), f is: \n%s", f);

и он будет работать. Обратите внимание, что источник функции заключен в скобки (""), чтобы сделать это выражением lvalue, а не объявлением.

Здесь http://jsfiddle.net/G2Q2g/8/

UPDATE:

eval(str) анализирует и выполняет строку как программу (термин ECMAScript). Объявление функции само по себе является объявлением, оно не имеет значения. Но это ( func() ... ) это выражение, которое имеет значение. И eval возвращает значение последнего выражения. Вот что вы видите здесь.