JavaScript的範圍和變數對象
變數對象
先來說說什麼是變數對象,變數對象中又儲存了什麼東西吧。
JavaScript中的執行環境包括全域執行環境和函數執行環境這兩種,每進入到一個執行環境都會建立一個變數對象,這個對象中記錄了在當前執行環境中可以訪問到的變數,它們以變數對象的屬性形式存在。也就是說這個變數對象成為“範圍”這個抽象概念的實體。
同時,變數對象中的屬性記錄是有一定先後順序的,並且屬性值到底是實際的值還是undefined也是分階段的(進入上下文(函數開始調用,但還未執行內部的具體代碼);執行代碼階段)。
1. 在全域環境中:
(1)函式宣告
(2)變數聲明—其值為undefined,一直到執行到這條語句的時候才被賦予具體值。
2. 函數環境中:
在進入執行環境的時候,變數對象會進行如下初始化:
(1)arguments對象,對象中的值被賦予具體的實參值
(2)函數的形參:變數對象的一個屬性,其屬性名稱就是形參的名字,其 值就是實參的值;對於沒有傳遞的參數,其值為undefined。
(3)函式宣告:變數對象的一個屬性,其屬性名稱和值都是函數對象建立出來的,其值為指向某個函數對象的引用;
(4)變數聲明:變數對象的一個屬性,其屬性名稱即為變數名,其值為undefined。
而在執行到函數內部的具體某個語句的時候,上面所述的值為undefined的變數,其值都會被賦予具體的值。
總結一句話:在進入內容相關的時候(比如進入到一個全域環境或者是調用某個函數),變數對象除了arguments,函數的聲明,以及參數被賦予了具體的屬性值,其它的變數屬性預設的都是undefined。
關於聲明提升
**
JavaScript具有“聲明提升”的特性。使用function或var進行的聲明會被提升至當前範圍的頂端。
**
其實理解這句話可以從進入到一個執行環境後都發生了什麼來看,正如上面所說,進入到一個上下文後,聲明的函數(值為函數對象的引用)和變數(其值為undefined)都被記錄在了變數對象中。到了執行代碼階段,一邊在範圍鏈中搜尋標識符,一邊執行到某個語句後把原本進入上下文階段值為undefined的變數更換為具體的值。
函式宣告提升
看看第(3)條,在進入到執行環境的時候,函式宣告就會被添加到變數對象中,其對應的屬性值是指向所聲明的函數對象的引用,如果在函式宣告的語句之前存在一條函數調用語句,引擎會在範圍中搜尋該標示符,並且在變數對象中成功尋找到了聲明的函數名,其值也是存在的,即指向建立的函數對象的引用。因此這樣寫代碼不會報錯,成功調用了函數。
而通過函數運算式方式,也就是說不是以function開頭,而是把函數賦值給了某個變數,因為在進入上下文階段是不會給變數賦予具體的值的(參看第(4)條),所以這個時候該變數的值為undefined,要等到執行代碼階段,執行到這條語句後才會將其值更換為指向函數對象的引用,因此如果調用函數的語句在前面則會報錯,此時在變數對象中搜尋標識符,其值還是undefined,當然不能成功調用了。
再來看一個變數聲明提升的例子
var a = Hello;function b() { alert(a); //undefined var a = World; alert(a); //World}b();
為什麼執行了b(),第一個alert(a)是undefined呢?來看看進入到全域執行環境中發生了什麼,全域環境的變數對象被填充為:
VO={
b:指向一個函數對象的引用,
a:undefined—>執行到該句話後變成”hello”
}
再來看看調用b()發生了什麼,其變數對象被填充為:
VO={
arguments:{},
a:undefined—->執行到具體語句後才被賦值為“world”
}
注意到第一次執行到 alert(a)時會在當前範圍中搜尋標識符a,發現已經存在一個a(則不會再沿著範圍鏈往外層搜尋了),其值是undefined。而執行到var a = “World”;時則被替換為了具體的值。
如果在b中去掉var,則會沿著範圍鏈搜尋到全域環境中的變數a,那麼兩個alert(a)都會輸出“hello”。