5

我正在查看第 13 节或 ECMAScript 规范 (v. 5)。匿名函数表达式初始化如下:

返回创建一个新的函数对象的结果,如 13.2 中指定的,参数由 FormalParameterListopt 指定,主体由 FunctionBody 指定。传入正在运行的执行上下文的 LexicalEnvironment 作为 Scope。如果 FunctionExpression 包含在严格代码中或其 FunctionBody 是严格代码,则传入 true 作为 Strict 标志。

这个逻辑与函数声明的初始化方式非常相似。但是,请注意命名函数表达式的初始化有多么不同。

  1. 让 funcEnv 成为调用 NewDeclarativeEnvironment 的结果,传递运行执行上下文的词法环境作为参数
  2. 设 envRec 为 funcEnv 的环境记录。
  3. 调用 envRec 的 CreateImmutableBinding 具体方法,传入 Identifier 的 String 值作为参数。
  4. 让闭包是创建一个新的函数对象的结果,如 13.2 中指定的,其参数由 FormalParameterListopt 指定,主体由 FunctionBody 指定。传入 funcEnv 作为 Scope。如果 FunctionExpression 包含在严格代码中或其 FunctionBody 是严格代码,则传入 true 作为 Strict 标志。
  5. 调用 envRec 的 InitializeImmutableBinding 具体方法,传递 Identifier 的 String 值和闭包作为参数。
  6. 返回关闭。

我知道命名/匿名函数表达式之间的一大区别是命名函数表达式可以从函数内部递归调用,但这就是我能想到的。为什么设置如此不同,为什么需要执行这些额外步骤?

4

2 回答 2

9

所有“跳舞”的原因很简单。

命名函数表达式的标识符需要在函数范围内而不是在外部可用。

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]

})();
于 2013-03-01T03:26:22.307 回答
1

两者之间的核心区别在于范围界定(虽然,如果没有别的,很想知道这样做实际上涉及了多少;) - 你已经正确指出命名/匿名函数表达式之间的主要区别是递归调用命名的容易

为什么我说轻松?好吧,实际上没有什么可以阻止您递归调用匿名函数,但这很简单:

//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 属性。

于 2013-02-28T07:42:44.550 回答