0%

範圍鏈 (The Scope Chain)

我們已經了解執行堆、執行環境和變數環境,今天我們就來認識什麼是範圍練 (The Scope Chain) 吧 !

先讓我們來看這個範例程式碼吧,當我們執行到 function b 時,會發生什麼事呢 ?

1
2
3
4
5
6
7
8
9
10
11
function b() {
console.log(myVar);
}

function a () {
var myVar = 2;
b();
}

var myVar = 1;
a();

執行後,我們會發現結果是在全域中的 1,可能有些人會認為結果是 undefined,因為沒有宣告 myVar;而有些人可能會認為是 2,因為是在 function a 中呼叫 b,但都不是,讓我來看看是為什麼吧 !

我們知道程式執行後,執行堆會是這樣的情況,由於 function b 沒有宣告任何東西,所以變數環境是空的,但這樣 b 是怎麼知道 myVar 的值是什麼呢 ?

1
2
3
4
5
6
7
---------------------------------------
| b 的執行環境 | |
---------------------------------------
| a 的執行環境 | myVar = 2 |
---------------------------------------
| 全域執行環境 | myVar = 1 |
---------------------------------------

外部環境 (outer environment)

我們要知道 JavaScript 不只會在目前的變數環境尋找變數,也會在另一個我們還沒提到的外部環境 (outer environment) 尋找,每個執行環境都會參照到一個外部環境,那 b 的外部環境是什麼呢 ?

在 function b 的情況中,他的外部環境會參照到全域執行環境,而 function a 也會是相同情況。

1
2
3
4
5
6
7
     ---------------------------------------  參照到外部環境
| b 的執行環境 | | -----
--------------------------------------- |
---- | a 的執行環境 | myVar = 2 | |
| --------------------------------------- |
---> | 全域執行環境 | myVar = 1 | <----
---------------------------------------

即便 a 的執行環境在 b 的下方,b 也不會去取下方 a 的 myVar 變數,這是因為詞彙環境 (lexical environment) 的關係,詞彙環境代表程式碼被寫出來的位置,在這個範例中,function b 的詞彙是在全域環境中,與 var myVar = 1; 在同一層級,它不在 function a 裡面,而是在最外層的全域中,當然,function a 的外部環境也是在全域,因為 a 的詞彙環境也是在全域。

所以,我們知道當執行環境找不到變數時,會參照到外部環境去尋找變數,而外部環境的判斷方式為程式實際上被寫在哪個地方。

注意:這裡別和執行環境搞混,執行環境不會根據你的程式碼寫在哪裡而建立,它會根據你何時呼叫有關;而外部環境是根據你的程式碼的位置決定你要參照到到哪個外部環境

範圍鏈 (scope chain)

我們已經了解外部環境是怎麼運作的,但有時我們的 function 會不斷互相呼叫,執行堆可能會疊的很高,而當我們要找某個變數時,會不斷透過外部環境一層一層向下尋找,直到找到變數時 (可能是在全域中,或是在某個 function 中找到),這整個外部環境尋找變數的過程,我們可以稱之為範圍鏈 (scope chain)

補充:範圍 (scope) 代表能夠取用這個變數的地方;鏈 (chain) 代表外部環境參照所形成的連結。

在這個的範例中,function b 透過外部環境找到全域的 myVar 的過程就是範圍鏈。

1
2
3
4
5
6
7
8
9
10
11
function b() {
console.log(myVar);
}

function a () {
var myVar = 2;
b();
}

var myVar = 1;
a();

那如果我們改變 function b 的詞彙環境時,把它放到 function a 中,會發生什麼事 ?

注意:如果我們在全域中呼叫 function b,會出現 b is not defined,這是因為在創造階段不會看 function 的內容,只會看 function 開始和結束的地方,所以 b 並不會被加到變數環境中。

1
2
3
4
5
6
7
8
9
10
11
12
function a () {
var myVar = 2;

function b() {
console.log(myVar);
}

b();
}

var myVar = 1;
a();

當我們執行程式碼時,全域執行環境建立,接著 function a 和 b 的執行環境建立,

1
2
3
4
5
6
7
---------------------------------------
| b 的執行環境 | |
---------------------------------------
| a 的執行環境 | myVar = 2 |
---------------------------------------
| 全域執行環境 | myVar = 1 |
---------------------------------------

接著 function b 執行到 console.log,myVar 會透過外部環境參照到 function a,因為 function b 的詞彙環境是在 a 中,而 a 仍然是參照到全域,

1
2
3
4
5
6
7
     ---------------------------------------
| b 的執行環境 | | ----
--------------------------------------- |
---- | a 的執行環境 | myVar = 2 | <---
| ---------------------------------------
---> | 全域執行環境 | myVar = 1 |
---------------------------------------

所以 b 在變數環境找不到 myVar,會透過範圍鏈往 function a 找,然後在 a 找到 myVar,所以會印出 2

1
2
3
4
5
6
7
     ---------------------------------------
| b 的執行環境 | | ----
--------------------------------------- |
---- | a 的執行環境 | myVar = 2 | <---
| ---------------------------------------
---> | 全域執行環境 | myVar = 1 |
---------------------------------------

如果我們把 a 的 myVar 拿掉,因為 a 是參照到全域執行環境,所以最終參照到全域的 myVar,結果為 1

1
2
3
4
5
6
7
     ---------------------------------------
| b 的執行環境 | | ----
--------------------------------------- |
---- | a 的執行環境 | | <---
| ---------------------------------------
---> | 全域執行環境 | myVar = 1 |
---------------------------------------

補充:外部環境簡單判斷,可以看父層是什麼,如果父層是全域,那就是參照到全域執行環境中,如果父層是 function,那便會參照到該 function 的執行環境中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function a () {
var myVar = 2;
// b 的父層為 a,所以 b 的外部環境為 a
function b() {
console.log(myVar); // 2
}
b();
}

// c 的父層為全域,所以 b 的外部環境為全域執行環境
function c () {
console.log(myVar); // 1
}

var myVar = 1;
a();
c();

總結

簡單來說,範圍鏈就是尋找變數的過程,當這個執行環境找不到,便會透過它的外部環境往下找,直到找到變數為止。