1. 程式人生 > 其它 >JavaScript 語法解析、AST、V8、JIT

JavaScript 語法解析、AST、V8、JIT

JavaScript是如何執行的

對於常見編譯型語言(例如:Java)來說,編譯步驟分為:詞法分析->語法分析->語義檢查->程式碼優化和位元組碼生成。

對於解釋型語言(例如JavaScript)來說,通過詞法分析 -> 語法分析 -> 語法樹,就可以開始解釋執行了。

具體過程是這樣的:

1.詞法分析是將字元流(char stream)轉換為記號流(token stream)

NAME "AST"  
EQUALS  
NAME "is Tree"  
SEMICOLON

2.語法分析成 AST (Abstract Syntax Tree),你可以在這裡試試http://esprima.org/

3.預編譯,當JavaScript引擎解析指令碼時,它會在預編譯期對所有宣告的變數和函式進行處理!並且是先預宣告變數,再預定義函式!

4.解釋執行,在執行過程中,JavaScript 引擎是嚴格按著作用域機制(scope)來執行的,並且 JavaScript 的變數和函式作用域是在定義時決定的,而不是執行時決定的。JavaScript 中的變數作用域在函式體內有效,無塊作用域;

function func(){
    for(var i = 0; i < array.length; i++){  
      //do something here.  
    }
    //此時 i 仍然有值,及 i == array.length  
    console.log(i); // 但在 java 語言中,則無效
}

JavaScript 引擎通過作用域鏈(scope chain)把多個巢狀的作用域串連在一起,並藉助這個鏈條幫助 JavaScript 直譯器檢索變數的值。這個作用域鏈相當於一個索引表,並通過編號來儲存它們的巢狀關係。當 JavaScript 直譯器檢索變數的值,會按著這個索引編號進行快速查詢,直到找到全域性物件(global object)為止,如果沒有找到值,則傳遞一個特殊的 undefined 值。

var scope = "global";
scopeTest();
function scopeTest(){  
    console.log(scope);  
    var scope = "local";  
    console.log(scope); 
}
列印結果:undefined,local;

https://www.98891.com/article-15-1.html

V8、JIT

我們常說的 V8 是 Google 釋出的開源 JavaScript 引擎,採用 C++ 編寫。SpiderMonkey(Mozilla,基於 C)、Rhino(Mozilla,基於 Java),而 Nodejs依賴於 V8 引擎開發,接下來的內容是 JavaScript 在 V8 引擎中的執行狀態,而類似的 JavaScript 現代引擎對於這些實現大同小異。

在本文的開頭提到了編譯型語言,解釋型語言。JavaScript 是解釋型語言且弱型別,在生成 AST 之後,就開始一邊解釋,一邊執行,但是有個弊端,當某段程式碼被多次執行時,它就有了可優化的空間(比如型別判斷優化),而不用一次次的去重複之前的解釋執行。 編譯型語言如 JAVA,可以在執行前就進行優化編譯,但是這會耗費大量的時間,顯然不適用於 Web 互動。

於是就有了,JIT(Just-in-time),JIT 是兩種模式的混合。

它是如何工作的呢:

1.在 JavaScript 引擎中增加一個監視器(也叫分析器)。監視器監控著程式碼的執行情況,記錄程式碼一共運行了多少次、如何執行的等資訊,如果同一行程式碼運行了幾次,這個程式碼段就被標記成了 “warm”,如果運行了很多次,則被標記成 “hot”。

2.(基線編譯器)如果一段程式碼變成了 “warm”,那麼 JIT 就把它送到基線編譯器去編譯,並且把編譯結果儲存起來。比如,監視器監視到了,某行、某個變數執行同樣的程式碼、使用了同樣的變數型別,那麼就會把編譯後的版本,替換這一行程式碼的執行,並且儲存。

3.(優化編譯器)如果一個程式碼段變得 “hot”,監視器會把它傳送到優化編譯器中。生成一個更快速和高效的程式碼版本出來,並且儲存。例如:迴圈加一個物件屬性時,假設它是 INT 型別,優先做 INT 型別的判斷

4.(去優化)可是對於 JavaScript 從來就沒有確定這麼一說,前 99 個物件屬性保持著 INT 型別,可能第 100 個就沒有這個屬性了,那麼這時候 JIT 會認為做了一個錯誤的假設,並且把優化程式碼丟掉,執行過程將會回到直譯器或者基線編譯器,這一過程叫做去優化。

優化程式碼圖例:

“hot” 程式碼

優化前

優化後

總結

明白一些基本原理能拓展出更多的東西,比如:

1.var a = 10; var b = 20; ==> var a=10, b=20; 這些改程式碼的好處是什麼,如何從原理解釋?

2.JavaScript 的函式和變數是在什麼時候宣告的,函式宣告和函式表示式的區別?

3.如何通過編譯器的優化原理,如何提高 JavaScript 的執行效率?