執行環境(有時也稱「環境」)是
JavaScript
中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個 與之關聯的變量對象,環境中定義的全部變量和函數都保存在這個對象中。雖然咱們 編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。javascript
全局執行環境是最外圍的一個執行環境。根據ECMAScript
實現所在的宿主環境不一樣,表示執行環境的對象也不同。在Web
瀏覽器中,全局執行環境被認爲是window
對象,所以全部全局變量和函數都是做爲window
對象的屬性和方法建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器——時纔會被銷燬)。前端
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將其環境彈出,把控制權返回給以前的執行環境。ECMAScript
程序中的執行流正是由這個方便的機制控制着。java
當代碼在一個環境中執行時,會建立變量對象的一個做用域鏈。做用域鏈的用途,是 保證對執行環境有權訪問的全部變量和函數的有序訪問。做用域鏈的前端,始終都是當前執行的代碼所在環境的變量對象。若是這個環境是函數,則將其活動對象做爲變量對象。瀏覽器
活動對象在最開始時只包含一個變量,即arguments
對象(這個對象在全局環境中是不存在的)。做用域鏈中的下一個變量對象來自包含(外部)環境,而再下一個變量對象則來自下一個包含環境。這樣,一直延續到全局執行環境;全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。函數
標識符解析是沿着做用域鏈一級一級地搜索標識符的過程。搜索過程始終從做用域鏈的前端開始, 而後逐級地向後回溯,直至找到標識符爲止(若是找不到標識符,一般會致使錯誤發生)。優化
var color = "blue";
function changeColor(){
if (color === "blue"){
color = "red";
} else {
color = "blue";
}
}
changeColor();
alert("Color is now " + color);
複製代碼
在這個簡單的例子中,函數changeColor()
的做用域鏈包含兩個對象:它本身的變量對象(其中定義着arguments
對象)和全局環境的變量對象。能夠在函數內部訪問變量 color
,就是由於能夠在這個做用域鏈中找到它。ui
雖然執行環境的類型總共只有兩種——全局和局部(函數),但仍是有其餘辦法來延長做用域鏈。這麼說是由於有些語句能夠在做用域鏈的前端臨時增長一個變量對象,該變量對象會在代碼執行後被移除。在兩種狀況下會發生這種現象。具體來講,就是當執行流進入下列任何一個語句時,做用域鏈就會獲得加長:url
這兩個語句都會在做用域鏈的前端添加一個變量對象。對with
語句來講,會將指定的對象添加到做用域鏈中。對catch
語句來講,會建立一個新的變量對象,其中包含的是被拋出的錯誤對象的聲明。下面看一個例子。spa
function buildUrl() {
var qs = "?debug=true";
with(location){
var url = href + qs;
}
return url;
}
複製代碼
在此,with
語句接收的是location
對象,所以其變量對象中就包含了location
對象的全部屬性和方法,而這個變量對象被添加到了做用域鏈的前端。buildUrl()
函數中定義了一個變量qs
。當在with
語句中引用變量href
時(實際引用的是location.href
),能夠在當前執行環境的變量對象中找到。當引用變量qs
時,引用的則是在buildUrl()
中定義的那個變量,而該變量位於函數環境的變量對象中。至於with
語句內部,則定義了一個名爲url
的變量,於是url
就成了函數執行環境的一部分,因此能夠做爲函數的值被返回。debug
JavaScript
沒有塊級做用域常常會致使理解上的困惑。在其餘類C
的語言中,由花括號封閉的代碼塊都有本身的做用域(若是用ECMAScript
的話來說,就是它們本身的執行環境),於是支持根據條件來定義變量。例如,下面的代碼在JavaScript
中並不會獲得想象中的結果:
if (true) {
var color = "blue";
}
alert(color); //"blue"
複製代碼
這裏是在一個if
語句中定義了變量color
。若是是在C
、C++
或Java
中,color
會在if
語句執行完畢後被銷燬。但在JavaScript
中,if
語句中的變量聲明會將變量添加到當前的執行環境(在這裏是全局環境)中。在使用for
語句時尤爲要牢記這一差別,例如:
for (var i=0; i < 10; i++){
doSomething(i);
}
alert(i); //10
複製代碼
對於有塊級做用域的語言來講,for
語句初始化變量的表達式所定義的變量,只會存在於循環的環境之中。而對於JavaScript
來講,由for
語句建立的變量i
即便在for
循環執行結束後,也依舊會存在於循環外部的執行環境中。
使用var
聲明的變量會自動被添加到最接近的環境中。在函數內部,最接近的環境就函數的局部環境;在with
語句中,最接近的環境是函數環境。若是初始化變量時沒有使用var
聲明,該變量會自動被添加到全局環境。以下所示:
function add(num1, num2) {
var sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //因爲 sum 不是有效的變量,所以會致使錯誤
複製代碼
以上代碼中的函數 add()
定義了一個名爲sum
的局部變量,該變量包含加法操做的結果。雖然結果值從函數中返回了,但變量sum
在函數外部是訪問不到的。若是省略這個例子中的var
關鍵字,那麼當add()
執行完畢後,sum
也將能夠訪問到:
function add(num1, num2) {
sum = num1 + num2;
return sum;
}
var result = add(10, 20); //30
alert(sum); //30
複製代碼
這個例子中的變量sum
在被初始化賦值時沒有使用var
關鍵字。因而,當調用完add()
以後,添加到全局環境中的變量sum
將繼續存在;即便函數已經執行完畢,後面的代碼依舊能夠訪問它。
當在某個環境中爲了讀取或寫入而引用一個標識符時,必須經過搜索來肯定該標識符實際表明什麼。搜索過程從做用域鏈的前端開始,向上逐級查詢與給定名字匹配的標識符。若是在局部環境中找到了該標識符,搜索過程中止,變量就緒。若是在局部環境中沒有找到該變量名,則繼續沿做用域鏈向上搜索。搜索過程將一直追溯到全局環境的變量對象。若是在全局環境中也沒有找到這個標識符,則意味着該變量還沒有聲明。
變量查詢也不是沒有代價的。很明顯,訪問局部變量要比訪問全局變量更快,因 爲不用向上搜索做用域鏈。JavaScript 引擎在優化標識符查詢方面作得不錯,所以這 個差異在未來恐怕就能夠忽略不計了。