你懂 JavaScript 嗎?#10 範疇(Scope)
17 Oct 2018本文會提到
- 什麼是「範疇」?範疇的功用是?
- 編譯器怎麼理解程式碼?
- 什麼是巢狀範疇?
- 從 LHS 與 RHS 來理解 JavaScript 查找變數的報錯機制。
範疇(Scope)
範疇(Scope)是指編譯器或 JavaScript 引擎藉由識別字名稱(identifier name)查找變數的一組規則。
編譯器怎麼理解程式碼?
編譯器會在程式執行前將程式碼由上到下逐行轉為電腦可懂的命令,稍後會執行這個編譯後的結果。注意,JavaScript 引擎會在每次執行前即時編譯程式碼(約幾毫秒 (ms) 而已),接著立刻執行編譯後的指令。
編譯有三步驟
- 語法基本單元化與語彙分析(tokenizing/lexing):將字串解析成 token,例如:
var a = 2;
就會解析var
、a
、=
、2
、;
。 - 剖析或稱語法分析(parsing):承上,將這些 token(
var
、a
、=
、2
、;
)組成抽象語法樹(abstract syntax tree,AST)
- 產生目的程式碼(code-generation):承上,將 AST 轉為可執行的程式碼,通常是機器語言,在這裡也會做最佳化。
範疇的功用是?
在編譯的過程中,JavaScript 引擎、編譯器和範疇會互相溝通以完成工作。它們各自負責的任務有
- JavaScript 引擎:負責整個編譯過程並執行程式碼。
- 編譯器:負責編譯三步驟-語法基本單元化與語彙分析、剖析或稱語法分析、產生目的程式碼。
- 範疇:負責維護變數清單。
範例如下,這裡有一段程式碼,在編譯這段程式碼時,JavaScript 引擎、編譯器和範疇三者會做什麼呢?
var a = 2;
- 在編譯的時候,編譯器會先到範疇詢問變數 a 是否存在,若不存在就宣告這個 a 變數。
- 接著,在執行階段,JavaScript 引擎先到範疇詢問變數 a 是否存在,若存在就將 2 設定給它;若 a 不存在就報錯。
其中,引擎對範疇的查找變數的動作,可分為兩種類型
- LHS(left-hand side):要查找的變數在指定動作的左邊,例如:
a = 2
的 a 在等號的左邊,就是執行 LHS 查找動作。 - RHS(right-hand side):變數不在指定動作的左邊,例如:
console.log(a)
,就是執行 RHS 查找動作。
備註:函式宣告(例如: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 報錯的原因。
當解析 identifier 失敗時
- 若是 RHS,則會丟出 ReferrenceError 的訊息。
- 若是 LHS,就會分為是否在嚴格模式(strict mode)的情況
- 在非嚴格模式下,會在全域建立這個變數。
- 在嚴格模式下,會丟出 ReferrenceError 的訊息。
還有一種狀況,不論在 LHS 和 RHS 下,操作不合法的行為時,就會丟出 TypeError 的訊息。
- LHS:重新設定已宣告為 const 變數,
const a = 2; a = 4;
的a = 4
會導致 TypeError。 - RHS:執行不是 function 的變數,
const b = 2; b();
的b()
會導致 TypeError。
範例
這裡有一個小範例,判斷哪裡發生了 LHS?哪裡發生了 RHS?
const foo = (a) => {
const b = a;
return a + b;
};
const c = foo(2);
答案是…
…
…
…
LHS
const c = ...
const b = ...
- 隱含的參數設定
a = 2
RHS
const b = a
其中的... = a
對 a 取值return a + b
其中要對 a 取值return a + b
其中要對 b 取值foo(a)
其中要對 foo 取得其函數
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- 範疇是指編譯器或 JavaScript 引擎藉由識別字名稱查找變數的一組規則。
- 編譯器會在程式執行前將程式碼由上到下逐行轉為電腦可懂的命令,而稍後執行的即是這個編譯後的結果。編譯有三步驟:語法基本單元化與語彙分析、剖析或稱語法分析、產生目的程式碼。並且,在編譯的過程中,JavaScript 引擎、編譯器和範疇會互相溝通以完成工作。
- 在查找變數的過程中,若在目前執行的範疇找不到這個變數的時候,就會往外層的範疇搜尋,持續搜尋直到找到為止,或直到最外層的全域範疇。
- 從 LHS 與 RHS 來理解 JavaScript 查找變數的報錯機制。
References
同步發表於2019 鐵人賽。