範圍鏈的意思就是「找不到就往外找」,至於哪裡是裡面、哪裡是外面則會應用到 JavaScript「執行環境(execution context)」的概念,若對此一觀念仍不熟悉,可先參考此篇文章。
範圍鏈(Scope Chain)
當一函式內的變數未在該函式的執行環境中宣告,會從同樣的詞彙環境(lexical environment)中開始依照調用棧(call stack)的規則逐層往外找,直到找到全域執行環境中的變數宣告為止;若在全域執行環境中仍未找到該變數宣告,則會顯示錯誤訊息。
現假設有一程式碼如下:
JavaScript 會如何執行這些程式碼呢?
全域執行環境
首先,在全域執行環境的創建階段,會先保留位置給 var 變數跟函式宣告,這裡並沒有 var 變數,因此會將「Learn1()」先放到記憶體中:
接著開始執行「Learn1()」範圍之外的程式碼,先是第一、二行的變數宣告,接著直接跳到第 14 行的「Learn1()」:
函式執行環境-Learn1()
讀到第 14 行的「Learn1()」之後,便進入「Learn1()」函式的執行環境,在創建階段先為該函式內的var變數跟函式宣告保留空間,這裡沒有var變數,因此將「Learn2()」放到記憶體中:
接著開始一行一行讀「Learn1()」內的程式碼,當中的變數「program」套用全域變數裡的「”JavaScript”」:
函式執行環境-Learn2()
讀到第12行的「Learn2()」之後,便進入「Learn2()」函式的執行環境,在創建階段先為該函式內的 var 變數跟函式宣告保留空間,這裡兩者皆無,因此直接讀取「Learn2()」的程式碼:
在上圖中,「Learn2()」當中的變數「program」套用全域變數裡的「”JavaScript”」,那「instrument」會套用「”piano”」還是「”flute”」呢?
在「Learn2()」中並未宣告「instrument」這個變數,因此會往外層找,在「Learn1()」裡找到賦值為「”piano”」的「instrument」,便將其套用到「Learn2()」的程式碼中:
最後顯示的結果如下:
如果將「Learn1()」裡的「instrument」變數宣告拿掉,「Learn2()」會再往外層找,套用全域執行環境裡被賦值為「”flute”」的「instrument」變數:
如果全域執行環境裡也沒有宣告「instrument」變數,便會顯示該變數為「not defined」:
但如果把「Learn2()」的函式宣告內容搬出「Learn1()」的函式執行環境,「instrument」這個變數會被賦值為「”flute”」還是「”piano”」呢?
「Learn2()」執行的位置看似仍在「Learn1()」的函式執行環境內,但因在全域執行環境的創建階段,「Learn2()」已經被放在記憶體中,因此「Learn2()」內要套用「instrument」變數時,會套用全域變數「”flute”」。
如上述因未在函式內宣告某變數而不斷往外找的過程,就是「範圍鏈(scope chain)」,又稱「閉包(closure)」,函式在哪個執行環境被宣告,就從哪裡開始找,一層一層往外找的過程以「調用棧(call stack)」方式進行。
現在我們來看另一範例:
全域執行環境
- 創建階段:因無 var 變數,故先將全域執行環境中的函式宣告「Learn1()」放進記憶體中。
- 執行階段:從第 1 行起逐行執行其他程式碼,以下圖來說,只有黃色區塊的程式碼會被執行。
執行到第 21 行為「Learn1()」,此時進入「Learn1()」的函式執行環境。
函式執行環境-Learn1()
- 創建階段:因無 var 變數,故先將「Learn1()」函式執行環境中的函式宣告「Learn2()」與「Learn3()」放進記憶體中。
- 執行階段:從第 5 行起逐一執行非函式宣告部分的程式碼,在第 6 行中直接套用「Learn1()」裡面「instrument」變數的賦值「”piano”」,「program」變數則往外套用全域執行環境中的「”JavaScript”」,因此顯示結果為「”Line 6: I am learning JavaScript and piano”」。
執行到第 18 行進入「Learn3()」,此時進入「Learn3()」的函式執行環境。
函式執行環境-Learn3()
- 創建階段:var 變數與函式宣告皆不存在,因此不需保留特定記憶體空間。
- 執行階段:從第 13 行起逐一執行程式碼,在第 14 行中直接套用「Learn3()」裡面「instrument」變數的賦值「”violin”」,「program」變數則往外套用全域執行環境中的「”JavaScript”」,因此顯示結果為「”Line 14: I am learning JavaScript and violin”」。
執行到第15行為「Learn2()」,此時進入「Learn2()」的函式執行環境。
函式執行環境-Learn2()
- 創建階段:var 變數與函式宣告皆不存在,因此不需保留特定記憶體空間。
- 執行階段:執行第 9 行程式碼,因「Learn2()」內未宣告「instrument」變數,因此套用「Learn2()」被宣告環境「Learn1()」中的「instrument」變數-「”piano”」,「program」變數則因「Learn1()」內也未宣告,持續往外套用全域執行環境中的「”JavaScript”」,因此顯示結果為「”Line 9: I am learning JavaScript and piano”」。