为什么匿名函数表达式和命名函数表达式的初始化方式如此不同?

时间:2013-02-28 07:04:48

标签: javascript function anonymous-function ecmascript-5 ecma262

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

  

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

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

  
      
  1. 让funcEnv成为调用NewDeclarativeEnvironment传递正在运行的执行上下文的词法环境的结果   参数
  2.   
  3. 让envRec成为funcEnv的环境记录。
  4.   
  5. 调用envRec的CreateImmutableBinding具体方法,将Identifier的String值作为参数传递。
  6.   
  7. 让闭包成为使用FormalParameterListopt指定的参数创建13.2中指定的新Function对象的结果   和BodyBody指定的主体。传入funcEnv作为范围。通过   如果包含FunctionExpression,则为true作为Strict标志   严格的代码或其FunctionBody是严格的代码。
  8.   
  9. 调用envRec的InitializeImmutableBinding具体方法,将Identifier和closure的String值作为参数传递。
  10.   
  11. 返回封闭。
  12.   

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

2 个答案:

答案 0 :(得分:9)

所有这些"跳舞的原因"很简单。

命名函数表达式的标识符需要在函数范围内,但不能在之外。

typeof f; // undefined

(function f() {
  typeof f; // function
})();

如何在功能中提供f

您无法在外部词汇环境中创建绑定,因为f不应在外部可用。而且你不能在内部变量环境中创建绑定,因为......它还没有创建;该函数在实例化时尚未执行,因此从未发生过具有NewDeclarativeEnvironment的10.4.3(输入函数代码)步骤。

所以这样做的方法是创建一个中间词汇环境,"继承"直接来自当前的一个,然后作为[[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]

})();

答案 1 :(得分:1)

两者之间的核心差异涉及范围界定(尽管如果没有别的,很奇怪看到这样做实际涉及多少;) - 你已经指出了名字/匿名之间的主要区别函数表达式是递归调用命名的 ease

为什么我说轻松?好吧,实际上没有什么能阻止你calling an anonymous function recursively,但它显然不是很漂亮:

//silly factorial, 5!
(function(n) {
  if (n<=1) return 1;
  return (n*arguments.callee(n-1)); //arguments.callee is so 1990s!
})(5);

事实上,那是exactly what MDN says in describing named function expressions

  

如果要引用函数体内的当前函数,   您需要创建一个命名函数表达式。这个名字就是这样   本地只对函数体(范围)。这也避免了使用   非标准的arguments.callee属性。