一般我們將 JavaScript 歸類為 “動態” 或 “解釋執行”語言,但事實上JavaScript也是一門編譯語言。JavaScript 引起不會有大量的(相比其它編譯語言)事件用來進行優化,因為與其他語言不同,JavaScript的編譯過程不是發生在構建之前的。對于 JavaScript 來說,大部門情況下編譯發生在代碼執行前的幾微妙的時間內。
在傳統的 編譯語言的流程中,程序中的一段源代碼在執行之前會經歷三個步驟:
- 分詞/詞法分析(Tokenizing/Lexing)
- 解析/語法分析 (Parsing)
- 代碼生成
比起那些編譯過程只有三個步驟的語言的編譯器,JavaScript 引擎要復雜得多
理解作用域
以下面的一段代碼為例。
var a = 2
-
引擎
: 負責整個JavaScript程序的編譯及執行過程 -
編譯器
負責語法分析及代碼生成等 -
作用域
負責收集維護由所有聲明的標識符(變量) 組成的一些列查詢。并實施一套非常嚴格的規格,確定當前執行的代碼對這些標識符的訪問權限。
但看到 var a = 2 時,引擎會認為這里有兩個完全不同的聲明,一個由編譯器在編譯時處理,另一個則由引擎在運行時處理.
編譯器會進行如下處理。
- 遇到 var a,會向當前作用域詢問是否已有一個該名稱的變量存在同一個作用域的集合中。如果有,編譯器會忽略該聲明,繼續進行編譯;否則它會要求作用域在當前作用域的集合中聲明一個新的變量,并命名為a。
- 編譯器會為引擎生成運行所需的代碼。引擎運行時會首先詢問作用域,在當前的作用域集合中是否存在一個叫作 a 的遍歷。如果是,引擎就會使用這個遍歷;如果否,引擎會繼續查找該變量。
如果引擎最終找到了 a 變量,就會將 2 賦值給它。否則引擎就會拋出一個異常!
LHS 和 RHS
編譯器在編譯過程的第二步中生成了代碼,引擎執行它時,會通過查找變量 a 來判斷它是否已經聲明過。查找的過程有作用域進行協助,但是引起執行怎樣的查找,會影響最終的查找結構
在上面的例子中,引擎會為變量 a 進行 LHS 查詢。另外一個查找的類型叫作 RHS。
如名稱一樣, 這里的 L 和 R 分別代表 left 和 right。指一個 賦值操作的左側和右側
- 如果查找的目的是對變量進行賦值,那么就會使用LHS查詢;(當變量出現在賦值操作的左側)
- 如果查找的目的是獲取變量的值,就會使用RHS查詢;(當變量出現在賦值操作的右側)
RHS 查詢與簡單地查找某個變量的值別無二致, 而LHS查詢則是試圖找到變量的容器本身,從而可以對其賦值
考慮以下代碼:
console.log(a)
上面代碼中 a 的引用是一個 RHS 引用,因為這里并沒有賦予任何值。相應地,需要查找并取得a的值,這樣才能將值傳遞給 console.log(...)
相反的,例如:
a = 10
這里的 a 的引用則是 LHS 引用,要為 = 2 這個賦值找到一個目標
下面是一個小例子:
function foo(a) {
var b = a
return a + b
}
var c = foo(2)
上面分別有幾處 LHS 查詢,幾處 RHS 查詢 ?
分別有 3 處 LHS, 4 處 RHS
LHS:
- c = ...
- a = ... 這個是容易忽略的一個 函數的形參,在調用函數時,會進行隱式變量分配.等同于 a=2
- b = ...
RHS:
- foo 函數的查找
- b = a 中 a 的查找
- return 中 a 的查找
- return 中 b 的查找
異常
在變量還沒有聲明的情況下,這兩種查詢的行為是不一樣的。
function foo(a) {
console.log( a + b )
b = a
}
foo( 2 )
在以上代碼中,第一對 b 進行 RHS 查詢的時候,是無法找到變量的。
如果在 RHS 查詢在所有嵌套的作用域中找不到所需要的變量,引擎就會拋出
ReferenceError
異常。ReferenceError
是非常重要的異常類型當引擎執行 LHS 查詢時,如果在所有作用域中找不到目標變量,就會在全局作用域中創建一個具有該名稱的變量,并將其返回給引擎,前提是在
非嚴格模式下
也就是:隱式全局變量
如果 RHS 查詢到一個變量,但是你嘗試對這個變量的值進行不合理的操作,也會引發錯誤,比如:
- 試圖對一個非函數類型的值進行函數調用,
- 或者引用 null 和 undefined 類型的值中的屬性,
那么引擎會拋出另一種類型的異常,叫做 TypeError
ReferenceError
同作用域判別失敗相關,而
TypeError
則代表作用域判別成功了,但是對結果的操作是非法或不合理的