ECMA中規定JS使用Scope Chain來實現closureScope Chain是JS中非常重要的機制JS中所有的標識符(Identifier)都是通過Scope Chain來查找值的下面的部分是關於ECMA及其實現SpiderMonkey和JScript如何用Scope Chain和[[scope]]來實現closure的
變量標識符查找
當我們在JS程序裡寫下像a++這樣的表達式時很難想象a的值和內存地址經過了復雜的查找過程才得以確定JS的所有標識符(通常是我們自己定義的變量名)在執行時都是從Scope Chain中查找值的這也是導致JS執行速度低的原因和JS實現靈活的動態特性的基礎Scope Chain是一個鏈表在JS執行時總是維護著Scope Chain來保證變量的可訪問性或者不可訪問性對於這個過程ECMA給出了很明確的描述(我翻譯了一下各位將就著看)
獲取Scope Chain的下一個對象如果沒有對象了則轉到第步
調用結果()的[[HasProperty]]方法 傳遞Identifier作為參數
如果結果()是true Reference(引用)類型的值它的base object是結果()而它的
property name是Identifier
跳到第步
返回一個Reference類型它的base object是null它的property name 是Identifier
注Reference(引用)類型的值是JS引擎使用的一種數據類型它分為base object和property name兩個部分假設在JS代碼中有objprop這樣的表達式那麼解釋成Reference類型base object是對象obj而property name是字符串prop
Scope Chain開始時被設為宿主對象所以在全局代碼中的變量就是宿主對象的屬性Scope Chain在執行時由JS引擎自動維護編譯型的引擎也會創建相應的運行時環境來做此事Scope Chain一般在函數調用或者執行進入with塊的時候改變
函數的執行
JS函數執行並非簡單地執行函數體(Function Body)中的JS代碼在此之前JS引擎會創建一個Activation Object這個對象將會被作為Scope Chain的頂端而函數的[[scope]]屬性中的對象將被鏈接為其後續的對象([[scope]]在函數定義時被確定稍後的內容是關於[[scope]]如何定義的)這意味著Function Body中的JS代碼所使用的標識符都是按照上一部分所描述的最先從Activation Object開始查找的Activation Object創建時只有一個arguments屬性它不會繼承Objectprototype的屬性和方法接下來的變量初始化(Variable Instantiation)將函數體中變量和函數聲明的結果添加到Activation Object作為屬性
函數的[[scope]]屬性
[[scope]]是ECMA規定的對象的私有屬性理論上只有JS引擎可以訪問但FireFox的幾個引擎(SpiderMonkey和Rhino)提供了私有屬性__parent__來訪問它(所以一會我們可以看一看它)盡管所有對象都有[[Scope]]但是它只對函數對象有用
對於函數聲明和匿名函數表達式來說[[scope]]就是它創建時的Scope Chain但是對於有名字的函數表達式[[scope]]頂端是一個新的JS對象(也就是繼承了Objectprototype)這個對象被鏈到函數創建時的Scope Chain它本身有一個屬性就是函數的名字這確保了函數內部的代碼可以無誤地訪問自己的函數名進行遞歸
舉個例子
function f()
{
return n>?n*f(n):;
}
var f=function f()
{
return n>?n*f(n):;
}
f的遞歸是不安全的而f的遞歸是安全的但是注意這僅僅是針對標准的規定而言事實上IE並沒有實現這個性質
From:http://tw.wingwit.com/Article/program/net/201311/13723.html