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

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

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

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

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

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


person contactmatt    schedule 28.02.2013    source источник


Ответы (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]

})();
person kangax    schedule 01.03.2013
comment
Есть и другие тонкости. Привязка имени функционального выражения доступна только для чтения, но вам по-прежнему разрешено объявлять переменную или функцию, используя то же имя в теле функционального выражения. Описание этой семантики (помните, что это только спецификация) требует использования дополнительной записи среды. - person Allen Wirfs-Brock; 02.03.2013
comment
Интересно. Но почему для этого требуется дополнительная запись среды? Если, например, привязка идентификатора NFE должна быть создана во время объявления функции (10.5) до шага 5, любые объявления переменных/функций в исходном коде просто переопределяют привязку NFE (5f), а не затеняют ее. Практически тот же эффект, не так ли? - person kangax; 02.03.2013

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

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

//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.

person Oleg    schedule 28.02.2013
comment
И arguments.callee не разрешено в строгом режиме. - person jfriend00; 28.02.2013
comment
@jfriend00: ... и я не говорю, что это так :) даже нет в строгом режиме это устарело / не одобряется. Afaic, это именно та причина, по которой именованные функциональные выражения - вещь, позволяющая рекурсию - я имею в виду, что идентификатор функции как именованный является только локальным для области действия функции! - person Oleg; 28.02.2013
comment
Я просто добавляю дополнительную информацию в качестве еще одной причины не использовать arguments.callee. Не нужно защищаться. - person jfriend00; 28.02.2013
comment
Я не звучу в обороне, я защищаюсь! #fairenough #pointtaken - person Oleg; 28.02.2013