返回文章列表

JavaScript Scope Chain:作用域鏈

9 分鐘
前端JavaScript

當程式碼存取一個變數時,JavaScript 不只在目前的作用域尋找,還會一層一層往外找,直到找到或報錯為止。

這個查找機制就是 Scope Chain (作用域鏈)。


什麼是作用域鏈

每個作用域都可以存取自己的變數,也可以存取外層作用域的變數。

這些作用域由內到外形成一條鏈,就是作用域鏈。

JavaScript
const a = 1;

function outer() {
  const b = 2;

  function inner() {
    const c = 3;
    console.log(a); // 1,往外找到全域
    console.log(b); // 2,往外找到 outer
    console.log(c); // 3,在自己的作用域找到
  }

  inner();
}

outer();

查找順序:

JavaScript 沿著這條鏈從內往外尋找,找到就停止,找不到就繼續往外,一直到全域作用域。

如果全域也找不到,就拋出 ReferenceError

JavaScript
function test() {
  console.log(x); // ReferenceError: x is not defined
}

test();

查找方向:只往外,不往內

作用域鏈的查找方向是單向的,只能從內往外,不能從外往內。

外層作用域無法存取內層作用域的變數:

JavaScript
function outer() {
  function inner() {
    const x = 10;
  }

  console.log(x); // ReferenceError: x is not defined
}

outer();

outer 無法存取 inner 內部的 x

多層巢狀的情況下,查找方向也是一樣的:

JavaScript
const level1 = "global";

function funcA() {
  const level2 = "funcA";

  function funcB() {
    const level3 = "funcB";

    function funcC() {
      console.log(level1); // "global"
      console.log(level2); // "funcA"
      console.log(level3); // "funcB"
    }

    funcC();
  }

  funcB();
}

funcA();

funcC 可以存取所有外層的變數,但 funcA 無法存取 funcBfuncC 的變數。


遮蔽效果 (Variable Shadowing)

當內層作用域宣告了與外層同名的變數,內層的變數會「遮蔽」外層的變數。

JavaScript
const name = "global";

function greet() {
  const name = "local";
  console.log(name); // "local"
}

greet();
console.log(name); // "global"

greet 內部,name 指向的是函式內宣告的 "local",外層的 "global" 被遮蔽了。

兩個 name 各自存在於不同的作用域,互不影響。

Variable Shadowing 本身不是錯誤,但同名變數可能讓程式碼變得難以閱讀,使用時要注意。


作用域鏈與靜態作用域

作用域鏈的結構在程式碼撰寫時就已決定,與函式在哪裡被呼叫無關。

這就是靜態作用域 (Lexical Scope) 的概念。

JavaScript
const name = "global";

function printName() {
  console.log(name);
}

function main() {
  const name = "local";
  printName(); // "global"
}

main();

printName 定義在全域,它的作用域鏈是:

即使 printName 是在 main 內被呼叫,它也不會存取 mainname,因為 main 不在它的作用域鏈上。

這個特性也是閉包 (Closure) 能夠運作的基礎。


總結

作用域鏈是 JavaScript 變數查找的核心機制:

  • 從目前作用域往外層尋找,直到全域
  • 查找方向只能由內往外,不能由外往內
  • 內層同名變數會遮蔽外層變數
  • 作用域鏈的結構由程式碼的撰寫位置決定,不受呼叫位置影響

理解作用域鏈之後,接下來通常會進一步學習:

  • Closure
  • Hoisting
  • Execution Context