Почему выражения анонимных функций и выражения именованных функций инициализируются так по-разному?
Я смотрю на раздел 13 или спецификацию ECMAScript (v. 5). Выражение анонимной функции инициализируется следующим образом:
Вернуть результат создания нового объекта Function, как указано в 13.2, с параметрами, указанными в FormalParameterListopt, и телом, указанным в FunctionBody. Передайте в LexicalEnvironment контекст выполняющегося выполнения как Scope. Передайте true как флаг Strict, если FunctionExpression содержится в строгом коде или если его FunctionBody - строгий код.
эта логика очень похожа на то, как инициализируется объявление функции. Однако обратите внимание, насколько отличается инициализация именованного выражения funciton.
- Пусть funcEnv будет результатом вызова NewDeclarativeEnvironment, передав в качестве аргумента лексическую среду текущего контекста выполнения
- Пусть envRec будет записью окружения funcEnv.
- Вызвать конкретный метод CreateImmutableBinding для envRec, передав в качестве аргумента значение String идентификатора.
- Пусть closure будет результатом создания нового объекта Function, как указано в 13.2, с параметрами, указанными в FormalParameterListopt, и телом, определенным в FunctionBody. Перейдите в funcEnv как Scope. Передайте true как флаг Strict, если FunctionExpression содержится в строгом коде или если его FunctionBody - строгий код.
- Вызвать конкретный метод InitializeImmutableBinding для envRec, передав в качестве аргументов значение String идентификатора и замыкание.
- Вернуться закрытие.
Я знаю одно из больших различий между выражениями именованных / анонимных функций в том, что выражения именованных функций можно вызывать рекурсивно изнутри функции, но это все, что я могу придумать. Почему настройки такие разные и зачем нужно делать эти дополнительные шаги?
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.