範圍鏈(Scope Chain)


範圍鏈的意思就是「找不到就往外找」,至於哪裡是裡面、哪裡是外面則會應用到 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)」方式進行。

現在我們來看另一範例:

全域執行環境

  1. 創建階段:因無 var 變數,故先將全域執行環境中的函式宣告「Learn1()」放進記憶體中。
  2. 執行階段:從第 1 行起逐行執行其他程式碼,以下圖來說,只有黃色區塊的程式碼會被執行。


執行到第 21 行為「Learn1()」,此時進入「Learn1()」的函式執行環境。

函式執行環境-Learn1()

  1. 創建階段:因無 var 變數,故先將「Learn1()」函式執行環境中的函式宣告「Learn2()」與「Learn3()」放進記憶體中。
  2. 執行階段:從第 5 行起逐一執行非函式宣告部分的程式碼,在第 6 行中直接套用「Learn1()」裡面「instrument」變數的賦值「”piano”」,「program」變數則往外套用全域執行環境中的「”JavaScript”」,因此顯示結果為「”Line 6: I am learning JavaScript and piano”」。

執行到第 18 行進入「Learn3()」,此時進入「Learn3()」的函式執行環境。

函式執行環境-Learn3()

  1. 創建階段:var 變數與函式宣告皆不存在,因此不需保留特定記憶體空間。
  2. 執行階段:從第 13 行起逐一執行程式碼,在第 14 行中直接套用「Learn3()」裡面「instrument」變數的賦值「”violin”」,「program」變數則往外套用全域執行環境中的「”JavaScript”」,因此顯示結果為「”Line 14: I am learning JavaScript and violin”」。

執行到第15行為「Learn2()」,此時進入「Learn2()」的函式執行環境。

函式執行環境-Learn2()

  1. 創建階段:var 變數與函式宣告皆不存在,因此不需保留特定記憶體空間。
  2. 執行階段:執行第 9 行程式碼,因「Learn2()」內未宣告「instrument」變數,因此套用「Learn2()」被宣告環境「Learn1()」中的「instrument」變數-「”piano”」,「program」變數則因「Learn1()」內也未宣告,持續往外套用全域執行環境中的「”JavaScript”」,因此顯示結果為「”Line 9: I am learning JavaScript and piano”」。
#javascript #範圍鏈







你可能感興趣的文章

Ruby: self

Ruby: self

下載react repo 出現npm i 錯誤 > <

下載react repo 出現npm i 錯誤 > <

同步 & 非同步(2) - event loop & macro / micro task

同步 & 非同步(2) - event loop & macro / micro task






留言討論