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