你懂 JavaScript 嗎?#10 範疇(Scope)

你所不知道的 JS

本文會提到

範疇(Scope)

範疇(Scope)是指編譯器或 JavaScript 引擎藉由識別字名稱(identifier name)查找變數的一組規則。

找東西

編譯器怎麼理解程式碼?

編譯器會在程式執行前將程式碼由上到下逐行轉為電腦可懂的命令,稍後會執行這個編譯後的結果。注意,JavaScript 引擎會在每次執行前即時編譯程式碼(約幾毫秒 (ms) 而已),接著立刻執行編譯後的指令。

編譯有三步驟

抽象語法樹(abstract syntax tree,AST)

範疇的功用是?

團隊合作

在編譯的過程中,JavaScript 引擎、編譯器和範疇會互相溝通以完成工作。它們各自負責的任務有

範例如下,這裡有一段程式碼,在編譯這段程式碼時,JavaScript 引擎、編譯器和範疇三者會做什麼呢?

var a = 2;
  1. 在編譯的時候,編譯器會先到範疇詢問變數 a 是否存在,若不存在就宣告這個 a 變數。
  2. 接著,在執行階段,JavaScript 引擎先到範疇詢問變數 a 是否存在,若存在就將 2 設定給它;若 a 不存在就報錯。

其中,引擎對範疇的查找變數的動作,可分為兩種類型

備註:函式宣告(例如:function foo() {...})並不是 LHS!這是因為在做函式宣告時,就同時做了宣告和值的定義,而非在執行階段設定其值。

巢狀範疇(Nested Scope)

若在目前執行的範疇找不到這個變數的時候,就會往外層的範疇搜尋,持續搜尋直到找到為止,或直到最外層的全域範疇(global scope);而這樣一層又一層的範疇就稱為「巢狀範疇」(nested scope)。

鴨子轉圈圈

如下,console.log(a + b) 中,b 的 RHS 無法在 foo 中解析完成,但可到全域範疇解析出來。

const foo = (a) => {
  console.log(a + b);
};

const b = 2;

foo(2); // 4

錯誤(Error)

為什麼需要理解 LHS 和 RHS 呢?這是因為要看懂 JavaScript 報錯的原因。

錯誤(Error)

當解析 identifier 失敗時

還有一種狀況,不論在 LHS 和 RHS 下,操作不合法的行為時,就會丟出 TypeError 的訊息。

範例

這裡有一個小範例,判斷哪裡發生了 LHS?哪裡發生了 RHS?

const foo = (a) => {
  const b = a;
  return a + b;
};

const c = foo(2);

答案是…

LHS

  1. const c = ...
  2. const b = ...
  3. 隱含的參數設定 a = 2

RHS

  1. const b = a 其中的 ... = a 對 a 取值
  2. return a + b 其中要對 a 取值
  3. return a + b 其中要對 b 取值
  4. foo(a) 其中要對 foo 取得其函數

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

References


同步發表於2019 鐵人賽


You-Dont-Know-JS javascript 你所不知道的JS 2019鐵人賽 你懂JavaScript嗎? 鐵人賽 You-Dont-Know-JS-Scope-and-Closures 你懂 JavaScript 嗎?2019 iT 邦幫忙鐵人賽 系列文