5

我正在查看section 13或ECMAScript规范(v。5)。如下初始化匿名函数表达式:为什么匿名函数表达式和命名函数表达式初始化如此不同?

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

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

  1. 令funcEnv是调用NewDeclarativeEnvironment传递运行的执行上下文的词法环境作为 论证的结果
  2. 令envRec funcEnv的环境记录。
  3. 调用envRec的CreateImmutableBinding具体方法,传递Identifier的String值作为参数。
  4. 让闭包是根据13.2中的规定创建一个新的Function对象的结果,其参数由FormalParameterListopt 指定,并由FunctionBody指定。作为范围传递funcEnv。如果函数表达式包含在 严格代码中,或者其FunctionBody是严格代码,则将 作为严格标记。
  5. 调用envRec的InitializeImmutableBinding具体方法,传递Identifier和closure的String值作为参数。
  6. 退货关闭。

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

回答

9

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

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

typeof f; // undefined 

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

如何让功能内的f可用?

您不能在外部词法环境中创建绑定,因为f不应在外部提供。而且你不能在内部变量环境中创建绑定,因为......它尚未创建;该函数在实例化时尚未执行,因此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] 

})(); 
+2

还有其他的微妙之处。函数表达式名称绑定是只读的,但您仍然可以在函数表达式的主体中声明一个使用相同名称的var或函数。描述这种语义(记住这只是一个规范)需要使用额外的环境记录。 – 2013-03-01 20:05:02

+0

有趣。但为什么这需要额外的环境记录?例如,如果NFE的标识符绑定是在第5步之前的函数声明(10.5)期间创建的,则源代码中的任何var/function声明只会覆盖NFE的绑定(5f)而不是将其隐藏。几乎相同的效果,不是吗? – kangax 2013-03-01 20:52:51

1

两个处理范围的核心区别在于(虽然如果没有别的事情,那么看到这样做实际涉及多少是很好奇的);而且你已经正确地指出了命名/匿名函数表达式是递归地调用指定函数的缓动

为什么说缓解?好吧,其实没有什么阻止你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属性的 。

+1

而'arguments.calle e'在严格模式下不允许。 – jfriend00 2013-02-28 07:52:35

+0

@ jfriend00:...我不是说它是:)即使*不*在严格模式下它已被弃用/皱眉。 Afaic这就是为什么命名函数表达式是事物的原因 - 允许递归 - 我的意思是函数标识符作为命名只是函数作用域的局部! – 2013-02-28 07:57:17

+0

我只是添加一个额外的信息作为另一个不使用'arguments.callee'的原因。没有必要听起来防守。 – jfriend00 2013-02-28 07:59:23