Где в JavaScript хранится неизменяемая запись привязки идентификатора в выражении именованной функции?

Недавно я столкнулся с некоторыми интересными фактами о именованных функциональных выражениях (NFE). Я понимаю, что имя функции NFE может быть доступно внутри тела функции, что делает рекурсию более удобной и экономит нам arguments.callee. И имя функции недоступно вне тела функции. Например,

var foo = function bar() {
    console.log(typeof bar);
}; 

typeof foo; // 'function'
typeof bar; // 'undefined', inaccessible outside the NFE
foo(); // 'function', accessible inside the NFE

Это хорошо задокументированная функция, и у kangax есть замечательный сообщение о NFE. и упомянул там это явление. Что меня больше всего удивляет, так это то, что имя функции NFE нельзя повторно связать с другими значениями в теле функции. Например,

(function foo() {
    foo = 5;
    alert(foo);
})(); // will alert function code instead of 5

В приведенном выше примере мы попытались повторно связать идентификатор foo с другим значением 5. Но это не удается! И я обратился к спецификации ES5 и обнаружил, что неизменяемая запись привязки была создана и добавлена ​​в записи среды лексической среды при создании NFE.

Проблема в том, что когда NFE ссылается на собственное имя функции внутри тела функции, это имя разрешается как свободная переменная. В приведенном выше примере foo упоминается внутри NFE, но это не формальный параметр и не локальная переменная этой функции. Таким образом, это свободная переменная, и ее запись привязки может быть разрешена через свойство [[scope]] NFE.

Итак, учтите, что если у нас есть другой идентификатор с тем же именем во внешней области видимости, возникает конфликт. Например,

var foo = 1;
(function foo() {
    alert(foo);
})(); // will alert function code rather than 1
alert(foo); // 1

Когда мы выполняем NFE, свободная переменная foo разрешается в функцию, с которой она связана. Но когда элемент управления выходит из контекста NFE, foo разрешается как локальная переменная во внешней области.

Итак, мой вопрос заключается в следующем:

  1. Где хранится неизменяемая запись привязки имени функции?
  2. Почему имя функции foo перевешивает var foo = 1 при разрешении внутри NFE? Хранятся ли их записи связывания в одной и той же лексической среде? Если да, то как?
  3. Что стоит за явлением, когда функция с именем foo доступна внутри, но невидима снаружи?

Может ли кто-нибудь пролить свет на это со спецификацией ES5? Я не нахожу большого обсуждения в Интернете.


person leslie.zhang    schedule 30.05.2015    source источник


Ответы (1)


Где хранится неизменяемая запись привязки имени функции?

В дополнительном лексическом окружении запись, которую вы не видите :-)

Почему имя функции foo перевешивает var foo = 1 при разрешении внутри NFE?

На самом деле это не так. Вы можете объявить новую локальную переменную var foo в области действия функции без каких-либо коллизий, но если вы этого не сделаете, свободная переменная foo разрешается в неизменяемую привязку. Однако он перевешивает глобальные переменные foo выше в цепочке областей видимости.

var foo = 1;
(function foo() { "use strict";
    var foo = 2;
    console.log(foo); // 2
}());
(function foo() { "use strict";
    console.log(foo); // function …
    foo = 2; // Error: Invalid assignment in strict mode
}());

Хранятся ли их записи связывания в одной и той же лексической среде?

Нет. Каждое именованное функциональное выражение заключено в дополнительную лексическую среду, которая имеет единственную неизменную привязку для имени функции, инициализированной функцией.

Это описано в разделе Определение функции (§13) спецификации. В то время как шаги для объявлений функций и выражений анонимных функций в основном таковы: «создать новый объект функции с телом этой функции, используя лексическую среду текущего контекста выполнения для Scope», именованная функция выражения более сложные:

  1. Пусть funcEnv будет результатом вызова NewDeclarativeEnvironment передача лексического окружения текущего контекста выполнения в качестве аргумента
  2. Пусть envRec будет записью среды funcEnv.
  3. Вызовите CreateImmutableBinding(N) конкретный метод envRec прохождения Identifier из функция в качестве аргумента.
  4. Пусть closure будет результатом создания нового объекта Function […]. Передайте funcEnv в качестве Области действия.
  5. Вызовите InitializeImmutableBinding(N,V) конкретный метод envRec прохождения Identifier из функция и closure в качестве аргументов.
  6. Вернуть closure.

Он создает дополнительную среду-оболочку только для функционального выражения. В коде ES6 с блочными областями:

var x = function foo(){};
// is equivalent to
var x;
{
    const foo = function() {};
    x = foo;
}
// foo is not in scope here

Что стоит за явлением, когда функция с именем foo доступна внутри, но невидима снаружи?

Неизменяемая привязка foo создается не в лексической среде текущего контекста выполнения, а в среде-оболочке, которая используется только для замыкания функционального выражения.

person Bergi    schedule 30.05.2015
comment
Вау, замечательный ответ, @Bergi! Спасибо за разъяснение моих сомнений! - person leslie.zhang; 31.05.2015