[原]JavaScript必備知識系列-作用域


執行環境和作用域

執行環境(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);至於函數表達式,則必須等到解析器執行到它所在的代碼行,才會真正被解釋執行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM