Почему выражения анонимных функций и выражения именованных функций инициализируются так по-разному?

Я смотрю на раздел 13 или спецификацию ECMAScript (v. 5). Выражение анонимной функции инициализируется следующим образом:

Вернуть результат создания нового объекта Function, как указано в 13.2, с параметрами, указанными в FormalParameterListopt, и телом, указанным в FunctionBody. Передайте в LexicalEnvironment контекст выполняющегося выполнения как Scope. Передайте true как флаг Strict, если FunctionExpression содержится в строгом коде или если его FunctionBody - строгий код.

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

  1. Пусть funcEnv будет результатом вызова NewDeclarativeEnvironment, передав в качестве аргумента лексическую среду текущего контекста выполнения
  2. Пусть envRec будет записью окружения funcEnv.
  3. Вызвать конкретный метод CreateImmutableBinding для envRec, передав в качестве аргумента значение String идентификатора.
  4. Пусть closure будет результатом создания нового объекта Function, как указано в 13.2, с параметрами, указанными в FormalParameterListopt, и телом, определенным в FunctionBody. Перейдите в funcEnv как Scope. Передайте true как флаг Strict, если FunctionExpression содержится в строгом коде или если его FunctionBody - строгий код.
  5. Вызвать конкретный метод InitializeImmutableBinding для envRec, передав в качестве аргументов значение String идентификатора и замыкание.
  6. Вернуться закрытие.

Я знаю одно из больших различий между выражениями именованных / анонимных функций в том, что выражения именованных функций можно вызывать рекурсивно изнутри функции, но это все, что я могу придумать. Почему настройки такие разные и зачем нужно делать эти дополнительные шаги?

2 ответа

Решение

Причина всего этого "танца" проста.

Идентификатор выражения именованной функции должен быть доступен в пределах области действия функции, но не снаружи.

typeof f; // undefined

(function f() {
  typeof f; // function
})();

Как вы делаете f доступны в функции?

Вы не можете создать привязку во внешней лексической среде, так как f не должно быть доступно снаружи. И вы не можете создать привязку во внутренней среде переменных, поскольку... она еще не создана; функция еще не выполняется в момент создания экземпляра, поэтому шаг 10.4.3 (Ввод кода функции) с его NewDeclarativeEnvironment никогда не выполнялся.

Таким образом, способ сделать это - создать промежуточную лексическую среду, которая "наследует" непосредственно от текущей и которая затем передается как [[Scope]] во вновь созданную функцию.

Это ясно видно, если разбить шаги из 13 на псевдокод:

// create new binding layer
funcEnv = NewDeclarativeEnvironment(current Lexical Environment)

envRec = funcEnv
// give it function's identifier
envRec.CreateImmutableBinding(Identifier)

// create function with this intermediate binding layer
closure = CreateNewFunction(funcEnv)

// assign newly created function to an identifier within this intermediate binding layer
envRec.InitializeImmutableBinding(Identifier, closure)

Так что лексическая среда внутри f (например, при разрешении идентификатора) теперь выглядит так:

(function f(){

  [global environment] <- [f: function(){}] <- [Current Variable Environment]

})();

С анонимной функцией это будет выглядеть так:

(function() {

  [global environment] <- [Current Variable Environment]

})();

Основное различие между этими двумя аспектами касается области видимости (хотя, если не сказать больше, любопытно посмотреть, сколько на самом деле в этом задействовано;) - и вы уже правы, указав, что основное различие между выражениями именованных / анонимных функций заключается в том, что простота рекурсивного вызова названных.

Почему я говорю легкость? Ну, на самом деле ничто не мешает вам рекурсивно вызывать анонимную функцию, но это просто не красиво:

//silly factorial, 5!
(function(n) {
  if (n<=1) return 1;
  return (n*arguments.callee(n-1)); //arguments.callee is so 1990s!
})(5);

Фактически, именно это MDN говорит при описании выражений именованных функций!

Если вы хотите обратиться к текущей функции внутри тела функции, вам нужно создать выражение с именованной функцией. Это имя является локальным только для тела функции (области видимости). Это также позволяет избежать использования нестандартного свойства arguments.callee.

Другие вопросы по тегам