什麼是範疇?
你有想過當你將值存進變數時,那變數放哪嗎?
而所謂的範疇就是一組規則用來定義變數儲存的位置。
接著我們正式進入第二本書的內容囉!
沒聽過編譯器也看過編譯器走路 (?
網路上搜尋 JS 大概幾乎直接都是看到說 JS 是一個直譯式的語言,但是你知道...他其實是編譯式語言嗎?
JS 特別的是他並不是像許多傳統的編譯式語言一樣,預先就編譯好,而是在快要執行的時候才編譯。
(聽起來有點臨時抱佛腳的感覺 xD)
傳統編譯式語言到底做了哪些事?
通常在執行前會經歷的是三個步驟,而這三個步驟大致上就稱為編譯。
以下粗略分享一下。
Tokennizing(語法基本單元化) and Lexing(語彙分析)
將一串字元拆分成有意義的組塊(chunks),譬如說var a = 17;
,他就可能會拆成 var
、a
、=
、2
和 ;
之類的。
空白有可能會有可能不會,會依照情境決定他是否有意義才決定。
Parsing(剖析)
基本上就是將上面拆出來的變成一個 AST(抽象與法樹)。
Code-Generation
接續上面的將拆出來的 AST 轉成可執行的一組機器指令,並實際建立出變數等等的再將值丟進變數。
而 JS 做得比上面說的三個單純的步驟來得複雜,在剖析過程中會有最佳化裡面可能包含了消除多餘元素(例如多餘的空白之類的)。
以範例來說
var a = 17;
這個述句在處理的方式會是如何?
主要有三個重要的角色
那我們就來看一下他們對於上面的述句負責的事~
Compiler
遇到 var a
,他就會問 Scope
,這個 a
,是否有在這特定的範疇集合內? 有的話就忽略宣告的動作繼續前進,沒有的話他就會要求 Scope
為那個範疇集合宣告 a
這個變數。Compiler
產生 Engine 看得懂的程式碼後會讓 Engine
去處理 a = 2
的指定式, Engine
就會跑去問 Scope
說目前範疇有沒有一個 a
的變數可以用? 如果有就拿來,沒有就會往外面的地方找,找到就恭喜老爺賀喜夫人,他就會把 2
塞給他,如果找不到的話 Engine
就會噴你錯誤。剛剛上面講到第二步時 Engine
去查找 a
是否被宣告,而這個問 Scope
的動作種類有分 LHS
(Lefthand side) 和 RHS
(Righthand side),這個查找的方向不同結果自然也就不同。
當一個變數出現在一個指定作業的左手邊,進行的就是 LHS
,反之則是 RHS
(但 RHS
不是說指定的在右手邊,而是說他不是左手邊)。
舉例來說
function foo(b){
var a = b;
return a + b;
}
var c = foo(2);
...
...
一起來找看看 LHS
和 RHS
有多少個吧!
...
...
好了嗎?
公佈答案囉~
...
這邊的 LHS
查找就有 3
個!
RHS
查找則有 4
個!
不是啊!
所以我說幹嘛要區分 LHS
和 RHS
,
我看不出來重點在哪?我哪裡需要?
主要是為了知道你在寫程式時,知道 JS 報的錯是為了什麼。
譬如說以下範例
function foo(a){
console.log(a+b);
b = a;
}
foo(2);
當 RHS
查找 b 這第一次出現的時候,他找不到所以稱之為為宣告的變數(undeclared),最終如果他在巢狀迴圈找不到,他就會拋出一個 ReferenceError
。
但是!!!
對於 LHS
而言的行為就很不一樣,
之前有介紹過嚴格模式對吧!? (沒有的話可以回去看~LHS
的行為模式會依照有沒有開啟嚴格模式而改變
ReferenceError
剛剛有提到當 Engine
找不到該變數在指定的範疇集合時,他就會往外找直到找到最外層全域變數為止。
例如下面的範例
function foo(a){
return a + b;
}
var b = 3;
foo(3);
foo
這 function 裡頭並沒有 b 這個變數,於是 Engine
就往外找到 b 這變數。
以上
感謝各位讀者讀到這~
明天繼續
最近公司各種誘惑啊~又是烤肉又是火鍋的 Orz
我會堅持住的 (吧 QQ
你所不知道的 JS|範疇與 Closures,this 與物件原型 (You Don't Know JS: this & Object Prototypes))