JavaScript 的 Scope Chain 和 Outer Environment

前言

執行環境介紹筆記中有提到, JavaScript 會在運行時建立執行環境(Execution Contexts),並將函式存進記憶體、為變數預留記憶體位置。需要注意的是,包在函式 {}裡面的變數或函式,要等到函式被呼叫、建立執行函式執行環境時,才會被存進記憶體中

範例

  • 範例一
    在這個範例中一共建立了三個執行環境,分別是: 全域執行環境、a 函式執行環境、b 函式執行環境。從範例中可以看出,同樣一個變數在不同的執行環境做宣告,他的值是獨立存在、不會互相影響的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function b() {
    var myVar = 3
    console.log(myVar)
    }

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

    var myVar = 1
    a()
    console.log(myVar)

    // 輸出結果:
    // 3
    // 1
  • 範例二
    這個範例和上個範例幾乎相同,唯一的差別是: b 函式裡面的 myVar 沒有進行宣告,直接賦值。按照範例一的結果,變數的作用域應該是獨立於各個執行環境的,但我在 b 函式裡面的賦值動作卻操作到全域執行環境的 myVar?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function b() {
    myVar = 3
    console.log(myVar)
    }

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

    var myVar = 1
    a()
    console.log(myVar)

    // 輸出結果:
    // 3
    // 3
  • 範例三
    這個範例和前面範例的差異也不大: b 函式裡面的沒有進行任何 myVar 宣告和賦值。在這種情況下,原本預期的輸出結果會出現 undefined ,但是這邊卻印出了全域 myVar 的值?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function b() {
    console.log(myVar)
    }

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

    var myVar = 1
    a()
    console.log(myVar)

    // 輸出結果:
    // 1
    // 1

Outer Environment

上面範例中狀況其實是由一個 JavaScript 的特性造成的。如果要使用一個當前執行環境(execution context)所沒有的變數時, JavaScript 會去 Outer Environment 尋找。但 Outer Environment 到底是指什麼呢?

執行環境介紹筆記中有提到,在函式執行環境的創建階段,除了會建立環境物件(內含被宣告的變數和函式),還有一個叫做外部環境(Outer Environment)的東西會被一併設定好。外部環境可以用函式(function)被「宣告」的執行環境來判斷(注意! 不是函式被執行所建立的執行環境)。

以範例二和範例三來看,執行環境和函式的宣告關係如下:

Execution Contexts Declare in memory Outer Environments
Global function a 、 function b 、 myVar N/A
function a myVar Global
function b N/A Global

因為在範例二和範例三中,function b 並沒有宣告 myVar ,所以當 function b 要用到 myVar 這個變數時, JavaScript 會到 function b 的外部環境,也就是全域執行環境(Global Execution Contexts)找到 myVar 來使用。

如果用函式被「宣告」的執行環境來判斷外部環境不容易理解的話,也可以用 Lexical Envirenments 來判斷,也就是函式被書寫的位置。如果函式寫在 Global 層級,那它的外部環境就是 Global;如果函式寫在另一個函式裡面,那它的外部環境就是一個函式。

  • 範例四
    如果把 function b 移到 function a 裡面宣告。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function a() {
    function b() {
    var myVar = 3
    }
    var myVar = 2
    b()
    }

    var myVar = 1
    a()
    如果用範例四來判斷外部環境,會呈現下面表格說明的關係:
    Execution Contexts Declare in memory Outer Environments
    Global function a 、 myVar N/A
    function a function b 、 myVar Global
    function b myVar function a

Scope Chain

Scope Chain 其實就是一個函式(function)向上追蹤外部環境(Outer Environments),直到找到全域執行環境為止所有的執行環境。

以範例四來說,整條 Scope Chain 就是: function b —> function a —> Global 。

參考資料

文章內容如有錯誤,歡迎留言討論!


本 Blog 上的所有文章除特别聲明外,均採用 CC BY-SA 4.0 協議 ,轉載請註明出處!