執行環境和範圍
執行環境(execution context)是javascript中最為重要的一個概念。執行環境定義了變數或函數有權訪問的其他資料,決定了它們各自的行為。每個執行環境都有一個與之關聯的變數對象(variable object),環境中定義的所有變數和函數都儲存在這個對象中。雖然我們編寫的代碼無法訪問這個對象,但是解析器在處理資料時會在後台使用它。
全域執行環境是最外圍的一個執行環境。根據ECMAScript實現所在的宿主環境不同,表示執行環境的對象也不一樣。在Web瀏覽器中,全域執行環境被認為是window對象,因為所有全域變數和函數都是作為window對象的屬性和方法建立的。某個執行環境中的所有代碼執行完畢後,該環境被銷毀,儲存在其中的所有變數和函數定義也隨之銷毀(全域執行環境知道應用程式退出-例如關閉網頁或瀏覽器-時才會被銷毀)。
局部執行環境,每個函數都有自己的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行後,棧將其環境彈出,把控制權返回給之前的執行環境。ECMASript,程式中的執行流正是由這個方便的機制控制著。
當代碼在一個環境中執行時,會建立變數對象的一個範圍鏈(scope chain)。範圍鏈的用途,是保障隊執行環境有權訪問的所有變數和函數的有序訪問。範圍鏈的前端始終都是在當前執行的代碼所在環境的變數對象。如果這個環境是函數,則將其使用中的物件(activation object)作為變數對象。使用中的物件在最開始時只包含一個對象,即arguments對象(這個對象在全域環境中是不存在的)。範圍鏈中的下一個變數對象來自包含(外部)環境,而再下一個變數對象則來自下一個包含環境,這樣,一直延續到全域執行環境;全域執行環境的變數始終都是範圍鏈中的最後一個對象。
標示符解析是沿著範圍鏈一級一級地搜尋標示符的過程。搜尋過程始終從範圍鏈的前端開始,然後逐級地向後回溯,直至找到標示符為止。
範圍鏈的本質是一個指向變數對象的指標列表,它只引用但不實際包含變數對象。
延長範圍鏈
雖然執行環境的類型總共只有兩種—全域和局部,但還是有其他方法來延長範圍鏈。這麼說是應為有些語句可以在範圍鏈的前端臨時增加一個變數對象,該變數對象會在代碼執行後被移除。在兩種情況下。具體來說,就是當執行流進入下列任何一個語句時,範圍鏈就會得意加長:
Try-catch語句的catch塊;
with語句
這兩個語句都會在範圍鏈的前端添加一個變數對象。隊with語句來說,會將制定的對象添加到範圍鏈中。隊catch語句來說,會建立一個新的變數對象,其中包含的是被拋出的錯誤對象的聲明。
改變函數範圍
每個函數都包含兩個非繼承而來的方法:apply()和call()。這兩個方法的用途就是在特定的範圍中調用函數,實際上等於設定函數體內的this對象的值。
函數綁定:一個日益流行,在很多外掛程式(jquery, backbone)中都能見到的一個技巧。函數綁定要建立一個函數,可以在特定的this環境中以指定參數調用另一個函數。它常常和回呼函數與事件處理常式一起使用,以便在將函數作為變數傳遞的同時保留代碼執行環境。
ECMAScript 5為所有函數定義了一個原生的bind()方法,進一步簡單了操作。
沒有塊級範圍
JavaScript沒有塊級範圍。在其他類C的語言中,由花括弧封閉的代碼塊都有自己的範圍(如果用ECMAScriptd的話來講,就是它們自己的執行環境),因而支援根據條件來定義變數。
if(true) {
var color = ‘blue’;
}
for(var i = 0; i < 10; i++) {
doSomething(i);
}
If for語句中的變數聲明會將變數添加到當前的執行環境,for迴圈執行結束後,也依舊會存在於迴圈外部的執行環境中。
使用var聲明的變數會自動被添加到最接近的環境中。在函數內部,最饑餓的環境就是環境的局部環境;在with語句中,最接近的是函數環境。如果初始設定變數時沒有使用var聲明,該變數會自動被添加到全域環境。
模仿塊級範圍
通過匿名函數可以用來模仿塊級範圍,文法如下:
(function() {
//這裡是塊級範圍
})()
以上代碼定義並理解調用了一個匿名函數。將函式宣告包含在一個圓括弧中,表示它實際上是一個函數運算式。而緊隨其後的另一個圓括弧會立即調用這個函數。
可以這樣理解匿名函數:
var someFunction = function() {\
//這裡是塊級範圍
};
someFunction();
首先定義了一個函數,然後立即調用它。定義函數的方式是建立一個匿名函數,並把匿名函數複製給變數someFunction。而調用函數的方式是在函數名稱後面添加一對圓括弧,即someFunction()。那在這裡是不是也可以用函數的值直接取代函數名呢?
function() {
//這裡是塊級範圍
}(); //報錯!
這段代碼會導致語法錯誤,是因為JavaScript將function關鍵字當作一個函式宣告的開始,而函式宣告後面不能跟圓括弧。然而,函數運算式的後面可以跟圓括弧。要將函式宣告轉換為函數運算式只要給它家還是那個一對圓括弧即可。
(function() {
//這裡是塊級範圍
})();
實際應用中,這樣寫法也可以
!function() {
}();
開頭歎號“!“也可以換成”;” “+” “-”等。只不過閱讀起來不如上面那種形式好理解。
PS:函式宣告與函數運算式
函式宣告與函數運算式區別在於:解析器子向執行環境中載入資料時對它們並非一視同仁。解析器會率先讀取函式宣告,並使其在執行任何代碼之前可以訪問,這個重要特徵就是函式宣告提升(function declaration hoisting);至於函數運算式,則必須等到解析器執行到它所在的程式碼,才會真正被解釋執行。