4

最近我遇到了一些关于命名函数表达式(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 Spec 发现在创建 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 中解析时,函数名称为何foo超过?var foo = 1它们的绑定记录是否存储在相同的词法环境中?如果是这样,怎么做?
  3. foo函数名内部可访问但外部不可见的现象背后是什么?

有人可以通过 ES5 规范对此有所了解吗?我在网上找不到太多讨论。

4

1 回答 1

2

函数名的不可变绑定记录存放在哪里?

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

在 NFE 中解析时,函数名称为何foo超过?var foo = 1

事实上它没有。您可以在函数范围内声明一个新的局部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. 让我们envRecfuncEnv环境记录。
  3. 调用作为参数传递函数的CreateImmutableBinding(N)具体方法。envRecIdentifier
  4. 让我们closure创建一个新的 Function 对象 […]。作为Scope传入funcEnv
  5. 调用传递函数的InitializeImmutableBinding(N,V)具体方法并作为参数。envRecIdentifierclosure
  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,而是在仅用于函数表达式周围的闭包的包装器环境中创建的。

于 2015-05-30T15:20:16.403 回答