[JavaScriptWeird]No.9 範圍鏈 Scope Chain

前言

這一篇要記錄的觀念是常常會讓人感到困惑,光看名詞就會讓人不知道在講什麼的「範圍鏈 (Scope Chain)」,但實際上如果照著課程走,看到最後會發現,啊原來就只是這樣!所以不要被名詞嚇到了哩。

範圍鏈 Scope Chain

現在我們已經了解「執行堆」、「執行環境」、「變數環境」,讓我們直接看一個範例,會相當眼熟:

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

No.2 的時候有提到「外部環境」,還記得「執行堆」的觀念嗎?當我們進入到 b 函式時,會製造一個執行環境,放到執行堆的上面,並把所有已經宣告的變數都放入它的「變數環境」,會在 b 函式的變數環境內尋找 myVar 這個變數,但 b 函式內 myVar 沒被宣告,此時會參考到 b 函式「外部環境」的 myVar ,因此印出來的結果會是 1 。

克服 JS 奇怪部分 截圖

很混亂嗎?不知道怎麼判斷「外部環境」?

還記得一開始提到的「詞彙環境」嗎,就是代表「程式碼被寫出來的實際位置」,這意味著 JavaScript 引擎會如何處理這些程式在記憶體中的位置、以及與其他程式的連結。

所以回到例子,函式 b 以詞彙上來說,它在全域環境之上,代表 b 函式不在 a 函式內,它與最後一行的 myVar = 1 同層級。

事實上執行環境的實際位置不重要,函式 b 可以在 函式 a 上,或者互換位置,重要的是「執行的順序會決定函式如何被呼叫、執行環境如何被創造」。

JavaScript 相當注重詞彙環境,當你需要某個執行環境內的某個變數時,如果沒辦法找到此變數,它會到外部環境尋找變數,直到執行堆最下方尋找。這是整個範圍鏈到外部環境的過程。

所以如果有許多函式互相呼叫,搜尋範圍鏈會不斷的向下移動,直到全域階層。如果這些函式是互相在內部被定義,也會逐層的在那些外部環境參照尋找,直到找到或者沒找到,這一整個過程就稱為「範圍鏈」

範圍 (Scope)」代表能夠取用這個變數的地方;「鏈 (Chain)」是外部環境參照的連結;「詞彙」這是實際上程式碼被寫出來的物理位置

克服 JS 奇怪部分 截圖

如果上面這個例子了解了,我們來點詞彙環境上的改變:

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

我們改變了 b 函式的詞彙環境,它在 a 函式內了。

這代表什麼?

這代表現在我們不能在全域的地方呼叫 b 函式,因為全域執行環境會尋找 b 函式,但 b 函式根本不再這個變數環境內, b 函式在 a 函式內。

我們又可以得知一點,當 JavaScript 創造全域執行環境時,它發現 a 函式,但不會看 a 函式內有什麼東西,會直接到 a 函式結束的地方繼續看下去。所以如果直接這樣寫:

1
b();

會得到 b is not defined 的錯誤。

如果呼叫 a 函式呢?

讓我們再來推論一次:

  • 當呼叫 a 函式,函式 a 的執行、變數環境被創造,此時 myVar 的值為 2
  • 當程式執行到呼叫 b 函式時,函式 b 的執行、變數環境被創造
  • 當執行函式 b 內的程式碼 console.log(myVar) ,當 JavaScript 往範圍鏈下面找時,它在函式 b 內找不到 myVar ,便轉向外部環境的函式 a 找,因此結果會是 myVar = 2

所以讓我們再變化一次:

1
2
3
4
5
6
7
8
function a(){  
function b(){
console.log(myVar)
}
b();
}
var myVar = 1;
a();

延續上面的情境,當 JavaScript 往範圍鏈下面找時,它在函式 b 內找不到 myVar ,便轉向外部環境的函式 a 找,這個時候函式 a 也找不到 myVar 了,但是 a 函式的外部環境參照是全域執行環境,因此找到 myVar = 1

後記:

至此,我總算是明白了 JavaScript 在我們沒看到的地方偷偷摸摸的為我們做了什麼事情,如果明白了這些,也對於工作上的除錯有相當大的幫助,因為我們了解了JavaScript 底層做了些什麼,所以當出現一個非預期的值,我們也能快速的除錯。

0%