編譯 (解析)
對于傳統編譯型語言(例如:Java)來說,編譯步驟分為:詞法分析->語法分析->語義檢查->代碼優化和字節生成。
對于解釋型語言(例如:JavaScript)來說,通過詞法分析和語法分析得到語法樹后,就可以開始解釋執行了。
詞法分析是將字符流(char stream)轉換為記號流(token stream),例如:
正常代碼
var result = testNum1 - testNum2;
詞法分析后:
NAME "result"
EQUALS
NAME "testNum1"
MINUS
NAME "testNum2"
SEMICOLON
可以拿自然語言來類比,詞法分析是一對一的硬性翻譯,比如一段英文,逐詞翻譯成中文,得到的是一堆記號流,還很難理解。進一步的翻譯,就需要語法分析了。生成語法樹例子:
正常代碼
if(typeof a == "undefined" ){
a = 0;
}else{
a = a;
}
alert(a);
語法樹
執行
經過編譯階段的準備,JavaScript代碼在內存中已經被構建為語法樹,然后JavaScript引擎就會根據這個語法樹結構邊解釋邊執行了。
在解釋過程中,JavaScript引擎是嚴格按著作用域機制(scope)來執行的。JavaScript語法采用的是詞法作用域 (lexcical scope),也就是說JavaScript的變量和函數作用域是在定義時決定的,而不是執行時決定的,由于詞法作用域取決于源代碼結構,所以JavaScript解釋器只需要通過靜態分析就能確定每個變量、函數的作用域,這種作用域也稱為靜態作用域(static scope)。但需要注意,with和eval的語義無法僅通過靜態技術實現,實際上,只能說JS的作用域機制非常接近lexical scope。
JavaScript引擎在調用執行每個函數時,都會創建一個執行環境(execution context)。執行環境中包含一個調用對象(call object), 調用對象是一個scriptObject結構,用來保存內部變量表varDecls、內嵌函數表funDecls、父級引用列表upvalue等語法分析結構(注意:varDecls和funDecls等信息是在語法分析階段就已經得到,并保存在語法樹中。函數調用執行時,會將這些信息從語法樹復制到 scriptObject上)。scriptObject是與函數相關的一套靜態系統,與函數實例的生命周期保持一致。
JavaScript引擎通過作用域鏈(scope chain)把多個嵌套的作用域串連在一起,并借助這個鏈條幫助JavaScript解釋器檢索變量的值。這個作用域鏈相當于一個索引表,并通過編號來存儲它們的嵌套關系。當JavaScript解釋器檢索變量的值,會按著這個索引編號進行快速查找,直到找到全局對象(global object)為止,如果沒有找到值,則傳遞一個特殊的undefined值。
如果函數引用了外部變量的值,則JavaScript引擎會為該函數創建一個閉包體(closure),閉包體是一個完全封閉和獨立的作用域,它不會在函數調用完畢后就被JavaScript引擎當做垃圾進行回收。閉包體可以長期存在,因此開發人員常把閉包體當做內存中的蓄水池,專門用來長期保存變 量的值。
只有當閉包體的外部引用被全部設置為null值時,該閉包才會被回收。當然,也容易引發垃圾泛濫,甚至出現內存外溢的現象。
編譯和執行的關系
例子:
alert(a); // 返回值undefined
var a =1;
alert(a); // 返回值1
為什么這段代碼執行不會報錯?
因為:JavaScript引擎解析腳本時,它會在預編譯期對所有聲明的變量和函數進行處理。
分析:由于變量聲明是在預編譯期被處理的,所以在執行期間對于所有代碼來說,都是可見的。但是,你也會看到,執行上面代碼,提示的值是 undefined,而不是1。這是因為,變量初始化過程發生在執行期,而不是預編譯期。在執行期,JavaScript解釋器是按著代碼先后順序來解釋執行的,如果在前面代碼行中沒有為變量賦值,則JavaScript解釋器會使用默認值undefined。由于在第二行中為變量a賦值了,所以在第三行代 碼中會提示變量a的值為1,而不是undefined。
//代碼1
testFun(); // 調用函數,返回值1
function testFun(){
alert(1);
}
//代碼2
testFun(); // 調用函數,返回語法錯誤
var testFun = function(){
alert(1);
}
分析:
代碼1,在函數聲明前調用函數也是合法的,并能夠被正確解析。
代碼2,定義的函數僅作為值賦值給變量testFun,所以在預編譯期,JavaScript解釋器只能夠為聲明變量f進行處理,而對于變量testFun的值,只能等到執行期時按順序進行賦值,自然就會出現語法錯誤,提示找不到對象testFun。