JavaScript中存储的命名函数表达式中标识符的不可变绑定记录在哪里?

Where is the immutable binding record of the identifier in a named function expression stored in 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 的精彩 post 并在那里提到了这种现象。最让我惊讶的是,NFE 的函数名不能与函数体中的其他值重新关联。例如,

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

在上面的例子中,我们试图用另一个值 5 重新绑定标识符 foo。但这失败了!我转向 ES5 规范,发现在创建 NFE 时,会创建一个不可变的绑定记录并将其添加到词法环境的环境记录中。

问题是,当 NFE 在函数体内引用它自己的函数名称时,该名称被解析为 自由变量 。在上面的例子中,foo 在 NFE 内部被引用,但它既不是这个函数的形参也不是局部变量。所以它是一个自由变量,它的绑定记录可以通过NFE的[[scope]] 属性来解析。

所以考虑一下,如果我们在外部作用域中有另一个同名的标识符,似乎会有一些冲突。例如,

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

当我们执行 NFE 时,自由变量 foo 被解析为它关联的函数。但是当控件退出 NFE 上下文时,foo 被解析为外部作用域中的局部变量。

所以我的问题如下:

  1. 函数名的不可变绑定记录存储在哪里?
  2. 在 NFE 内部解析时,为什么函数名称 foovar foo = 1 重?它们的绑定记录是否存储在相同的词法环境中?如果是这样,如何?
  3. 函数名foo内部可访问外部不可见的现象是什么原因?

有人可以用 ES5 规范阐明这一点吗?我在网上没有找到太多讨论。

Where is the immutable binding record of the function name stored?

在您看不到的额外词法环境记录中:-)

How come the function name foo outweigh var foo = 1 when resolved inside 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
}());

Are their binding records stored in the same lexical environment?

没有。每个命名的函数表达式都包含在一个额外的词法环境中,该环境具有一个单一的、不可变的函数名称绑定,用函数初始化。

规范的 Function Definition (§13) 部分对此进行了描述。函数声明和匿名函数表达式的步骤基本上是"create a new function object with that function body using the current execution context's lexical environment for the Scope",而命名函数表达式则更复杂:

  1. funcEnv为调用NewDeclarativeEnvironment passing the running execution context’s Lexical Environment作为参数的结果
  2. envRecfuncEnv的环境记录。
  3. 调用envRecCreateImmutableBinding(N)具体方法,传递函数的Identifier作为参数。
  4. closure 成为创建新 Function 对象 […] 的结果。 funcEnv 作为 Scope 传入。
  5. 调用envRecInitializeImmutableBinding(N,V)具体方法,传递函数的Identifierclosure作为参数。
  6. Return closure.

它确实为函数表达式构造了一个额外的包装器环境。在具有块作用域的 ES6 代码中:

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

What's behind the phenomenon that function name foo is accessible inside but invisible outside?

foo 不可变绑定不是在当前执行上下文的词法环境中创建的,而是在仅用于函数表达式周围的闭包的包装器环境中创建的。