語彙範疇:定義於編寫時期。JS 採用此種模型。
動態範疇:不在意宣告,而取決於從何處被呼叫。(附錄 A)
function foo() { console.log(a); // 語彙範疇:印出 2;動態範疇:印出 3。 } function bar() { var a = 3; foo(); } var a = 2; bar();
語彙分析時期
語彙範疇是在語彙分析時期 (lexing) 定義的範疇。視變數與區塊在寫作時期被放置於何處來決定。語彙分析時期能知道所有識別字於何處宣告,以預測執行過程中會如何被查找。
- Bubble 1:foo
- Bubble 2:a, bar, b
- Bubble 3:c
查找
- 遮蔽:相同識別字可能出現在不同的範疇中,但只要找到第一個就會停止動作。意即,內層的識別字會遮蔽其他外層識別字。
- 全域變數:全域變數會自動成為全域物件的屬性 (e.g.
window.a
),因此可以拿來避免變數被遮蔽而取不到的問題。
查找動作永遠從當時執行的範疇開始,並往外或往上尋找,直到找到第一個符合的為止。
查找動作只適用於一級識別字,如 foo.bar.baz
,只會查找 foo。後續交由物件屬性存取規則繼續解析。
語彙範疇作弊技巧
eval
eval 可以傳入一個字串作為參數,並將該字串內容視為程式原本已經編寫好的程式碼。
function foo(str, a) { eval(str); // var b = 3; console.log(a, b); } var b = 2; foo('var b = 3;', 1); // 1 3
var b = 3;
被視為一直都在那裏的程式碼,b 因此遮蔽了外層全域的 b。
eval 能在執行時期修改編寫時期的語彙範疇。
with
with 接受一個物件,並把物件作為獨立的語彙範疇看待,將物件屬性視為該範疇下的識別字。
function foo(obj) { with (obj) { a = 2; } } var o2 = { b: 3 }; foo(o2); console.log(o2.a); // undefined console.log(a); // 2,全域值外漏
考慮以上程式碼,當在範疇中找不到 a 時,LHS 查找後自動產生全域變數 a。
with 能在執行時期將物件做為一個範疇來看待。
效能
eval 和 with 兩者在執行時期修改了編寫時期的語彙範疇;或是建立了新的語彙範疇。
Engine 在編譯時期會進行最佳化,事先確定所有變數和宣告的位置。然而若它發現了 eval 和 with,代表它原先做的靜態分析可能都是無效的,於是它乾脆放棄了最佳化,導致效能下降。
複習
- 請說明語彙範疇。
- eval 與 with 的缺點。