上一篇文章中介紹了Execution Context中的三個重要部分:VO/AO,scope chain和this,並詳細的介紹了VO/AO在JavaScript代碼執行中的表現。
本文就看看Execution Context中的scope chain。
做用域
開始介紹做用域鏈以前,先看看JavaScript中的做用域(scope)。在不少語言中(C++,C#,Java),做用域都是經過代碼塊(由{}包起來的代碼)來決定的,可是,在JavaScript做用域是跟函數相關的,也能夠說成是function-based。
例如,當for循環這個代碼塊結束後,依然能夠訪問變量」i」。
1
2
3
4
5
|
for(var i = 0; i < 3; i++){
console.log(i);
}
console.log(i); //3
|
對於做用域,又能夠分爲全局做用域(Global scope)和局部做用域(Local scpoe)。
全局做用域中的對象能夠在代碼的任何地方訪問,通常來講,下面狀況的對象會在全局做用域中:
- 最外層函數和在最外層函數外面定義的變量
- 沒有經過關鍵字」var」聲明的變量
- 瀏覽器中,window對象的屬性
局部做用域又被稱爲函數做用域(Function scope),全部的變量和函數只能在做用域內部使用。
1
2
3
4
5
6
7
8
9
|
var foo = 1;
window.bar = 2;
function baz(){
a = 3;
var b = 4;
}
// Global scope: foo, bar, baz, a
// Local scope: b
|
做用域鏈
經過前面一篇文章瞭解到,每個Execution Context中都有一個VO,用來存放變量,函數和參數等信息。
在JavaScript代碼運行中,全部用到的變量都須要去當前AO/VO中查找,當找不到的時候,就會繼續查找上層Execution Context中的AO/VO。這樣一級級向上查找的過程,就是全部Execution Context中的AO/VO組成了一個做用域鏈。
因此說,做用域鏈與一個執行上下文相關,是內部上下文全部變量對象(包括父變量對象)的列表,用於變量查詢。
1
|
Scope = VO/AO + All Parent VO/AOs
|
看一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var x = 10;
function foo() {
var y = 20;
function bar() {
var z = 30;
console.log(x + y + z);
};
bar()
};
foo();
|
上面代碼的輸出結果爲」60″,函數bar能夠直接訪問」z」,而後經過做用域鏈訪問上層的」x」和」y」。
- 綠色箭頭指向VO/AO
- 藍色箭頭指向scope chain(VO/AO + All Parent VO/AOs)
再看一個比較典型的例子:
1
2
3
4
5
6
7
8
9
10
|
var data = [];
for(var i = 0 ; i < 3; i++){
data[i]=function() {
console.log(i);
}
}
data[0]();// 3
data[1]();// 3
data[2]();// 3
|
第一感受(錯覺)這段代碼會輸出」0,1,2″。可是根據前面的介紹,變量」i」是存放在」Global VO」中的變量,循環結束後」i」的值就被設置爲3,因此代碼最後的三次函數調用訪問的是相同的」Global VO」中已經被更新的」i」。
結合做用域鏈看閉包
在JavaScript中,閉包跟做用域鏈有緊密的關係。相信你們對下面的閉包例子必定很是熟悉,代碼中經過閉包實現了一個簡單的計數器。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function counter() {
var x = 0;
return {
increase: function increase() { return ++x; },
decrease: function decrease() { return --x; }
};
}
var ctor = counter();
console.log(ctor.increase());
console.log(ctor.decrease());
|
下面咱們就經過Execution Context和scope chain來看看在上面閉包代碼執行中到底作了哪些事情。
1. 當代碼進入Global Context後,會建立Global VO
.
- 綠色箭頭指向VO/AO
- 藍色箭頭指向scope chain(VO/AO + All Parent VO/AOs)
2. 當代碼執行到」var cter = counter();」語句的時候,進入counter Execution Context;根據上一篇文章的介紹,這裏會建立counter AO,並設置counter Execution Context的scope chain
3. 當counter函數執行的最後,並退出的時候,Global VO中的ctor就會被設置;這裏須要注意的是,雖然counter Execution Context退出了執行上下文棧,可是由於ctor中的成員仍然引用counter AO(由於counter AO是increase和decrease函數的parent scope),因此counter AO依然在Scope中。
4. 當執行」ctor.increase()」代碼的時候,代碼將進入ctor.increase Execution Context,併爲該執行上下文建立VO/AO,scope chain和設置this;這時,ctor.increase AO將指向counter AO。
- 綠色箭頭指向VO/AO
- 藍色箭頭指向scope chain(VO/AO + All Parent VO/AOs)
- 紅色箭頭指向this
- 黑色箭頭指向parent VO/AO
相信看到這些,必定會對JavaScript閉包有了比較清晰的認識,也瞭解爲何counter Execution Context退出了執行上下文棧,可是counter AO沒有銷燬,能夠繼續訪問。
二維做用域鏈查找
經過上面瞭解到,做用域鏈(scope chain)的主要做用就是用來進行變量查找。可是,在JavaScript中還有原型鏈(prototype chain)的概念。
因爲做用域鏈和原型鏈的相互做用,這樣就造成了一個二維的查找。
對於這個二維查找能夠總結爲:當代碼須要查找一個屬性(property)或者描述符(identifier)的時候,首先會經過做用域鏈(scope chain)來查找相關的對象;一旦對象被找到,就會根據對象的原型鏈(prototype chain)來查找屬性(property)。
下面經過一個例子來看看這個二維查找: