首先來幾個名詞解釋:
- 作用域[scope]:每一個js函數都是一個對象,對象中有些屬性我們可以訪問,但有些不可以,這些屬性僅供js引擎存取,[[scope]]就是對象其中一個屬性。[[scope]]指的就是我們所說的作用域,其中存儲了運行期上下文的集合。
- 作用域鏈:[[scope]]中所存儲的執行期上下文對象的集合,這個集合呈鏈式鏈接,我們把這種鏈式鏈接叫做作用域鏈。
- 運行期上下文:當函數執行時,會創建一個稱爲執行期上下文的內部活動對象(簡稱AO),一個執行期上下文定義了一個函數執行時的環境;
- 查找變量:從作用域鏈的頂端依次向下查找;
描述
以下面的代碼塊爲例:
function test1() {
function test2() {
var test2Num = 234;
}
var test1Num = 123;
test2();
}
var globalNum = 100;
test1();
test1函數是一個對象,所以它一定有[scope]這個屬性。當它被定義時時,形成包括全局活動對象的作用域鏈: test1[[scope]] -- > 0: G0 { } 如下圖:
當test1被調用即被執行時,會創建一個包括arguments,test2()和test1Num的活動對象,簡稱test1AO,此時,它佔據了test1作用域鏈的最頂部:如圖
此時test2開始被定義,它也形成了自己的定義域鏈,但是它保存的是它所在環境的作用域鏈,也就是test1,如圖:
當test2被調用時,test2作用域鏈中,會產生包括 arguments 和 test2Num 的test2的AO,如圖:
到這裏,上面的過程可以描述爲:
小結
很明顯,作用域鏈本質上是一個指向變量對象的指針列表。並且外部函數的活動對象,即AO始終處於第二位,外部函數的外部函數的AO處於第三位,全局變量對象處在最外層,當前函數的AO處於第一位。其中需要注意的幾點有:
- 內部的函數會有外部的AO;在內部函數被定義時,會保存所在外部的AO;被執行時,會生成自己的AO,並且自己的AO在作用域鏈最頂端;
- 在不同的時刻調用同一個函數生成的AO是不同的;所以每個函數執行時的上下文都是不同的;
- 當函數執行完畢,它所產生的執行上下文被銷燬;內存中僅存全局作用域;