原文鏈接 How JavaScript works: an overview of the engine, the runtime, and the call stack by Alexander Zlatkovjavascript
幾周以前咱們開始了一系列的文件旨在深刻了解JavaScript和它是如何運行的:咱們認爲,經過了解JavaScript的組成部分以及它們如何一塊兒發揮做用,你可以編寫出更好的代碼和應用。html
這一系列的第一部提供了引擎,運行時和調用棧的概覽。第二篇將深刻了解V8引擎。咱們還將提供一些有關如何編寫更好的JavaScript代碼的快速提示,這也是咱們SessionStack(做者所在公司)開發團隊在構建產品時遵循的最佳作法。java
JavaScript引擎是執行JavaScript代碼的程序或解釋器。 JavaScript引擎能夠實現爲標準解釋器,也能夠做爲即時編譯器將JavaScript編譯爲某種形式的字節碼。git
如下列表是實現了JavaScript引擎的流行項目github
V8引擎是由Google創建的開源工程,使用C++編寫。該引擎在Chrome內部使用。和其餘引擎不同,V8也被用於流行的Node.js中。編程
在V8的5.9版本出來以前,引擎使用兩種編譯器(後續版本中已經使用TurboFan 替代了前二者, 這裏終於原文進行翻譯,最新的編譯器你們請點前面的鏈接來學習):數組
V8引擎在內部使用了多線程:瀏覽器
當首次執行JavaScript代碼的時候,V8的full-codegen直接原模原樣的轉義解析了的JavaScript代碼爲機器碼。這使得它能快速的執行機器碼。請注意,V8不會以這種方式使用中間字節碼錶示,從而無需解釋器。緩存
當你的代碼執行了一段時間,探查器線程聚集了足夠的數據肯定那個方法須要被優化安全
下一步,Grankshaft 開始在另一個線程進行優化。它將JavaScript抽象語法樹轉換爲稱爲Hydrogen的高級靜態單分配(SSA)表示形式,並嘗試優化該Hydrogen圖。大多數優化都在此級別上完成。
第一個優化是儘量的提早內聯儘量多的代碼。內聯是將調用部分(調用函數的代碼行)替換爲被調用函數的主體的過程。這個簡單的步驟可以使後續優化變得更有意義。
JavaScript是基於原型的語言:沒有使用克隆建立的類和對象。 JavaScript仍是一種動態編程語言,這意味着能夠在實例化對象後輕鬆地添加或刪除屬性。
大多數JavaScript解釋器使用類字典的結構(基於哈希函數)將對象屬性值的位置存儲在內存中。與非動態編程語言(例如Java或C#)相比,在JavaScript使用這種結構檢索屬性的值在計算上更加昂貴。在Java中,全部對象屬性都是在編譯以前由固定的對象結構肯定的,而且沒法在運行時動態添加或刪除(C#具備動態類型,這是另外一個主題)。結果就是,屬性的值(或指向這些屬性的指針)能夠做爲連續緩衝區存儲在內存中,而且在每一個緩衝區之間具備固定偏移量。能夠根據屬性類型輕鬆肯定偏移量的長度,可是在JavaScript中這是不可能的,由於JavaScript在運行時能夠更改屬性類型。
因爲使用字典查找對象屬性在內存中的位置效率很低,所以V8改用了另外一種方法:隱藏類。隱藏類的工做方式相似於Java之類的語言中使用的固定的對象結構(類),但它們是在運行時建立的。如今,讓咱們看看它們的實際外觀:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
複製代碼
一旦"new Point(1,2)" 執行了,V8將會建立一個隱藏的類稱之爲「C0」
C0類上尚未任何屬性,因此"C0"是空的。
第一條語句「this.x = x」執行(在Point 函數內部),V8將會基於「 C0」建立第二個隱藏類「C1」,「C1」描述了能夠找到X屬性的內存位置(至關於對象指針)。在這個例子中,「x」能夠存儲的偏移量是0,也就是說能夠把在內存中的Point對象看爲一個連續的緩衝區,第一個偏移值就與「x」相對應。若是屬性"x"添加到對象"point"中,V8也會經過「類轉換(class transition)」更新"C0",將隱藏類從「C0」轉換到「C1」。如今隱藏類變成了「C1」。
當語句「this.y = y」 (在Point 函數內部,this.x=x後面)被執行的時候,上訴步驟會重複執行。
新的隱藏類「C2」被建立,「類轉換」將被應用於「C1」,此時屬性「y」被加入Point對象中(該對象已經包含了屬性x)。隱藏類從「C1「轉換到「C2」。
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
p1.a = 5;
p1.b = 6;
var p2 = new Point(3, 4);
p2.b = 7;
p2.a = 8;
複製代碼
如今,你假設p1和p2會應用相同的隱藏類和類轉換。可是,這是不正確的,對於p1,首先添加的是a屬性,而後添加的b屬性,可是對於p2首先添加的是b屬性而後添加的是a屬性。所以,p1和p2是不一樣的隱藏類和不一樣的類轉換。在這種狀況下,最好以相同的順序初始化動態屬性,以便複用隱藏類。
V8充分利用另外一種稱爲「內聯緩存」的優化動態語言的技術。內聯緩存依賴於這種情形:對相同方法的調用每每發生在相同類型的對象上。
內聯緩存的深刻解釋請參考這裏。
咱們將介紹內聯緩存的通常概念(以防你沒有時間瞭解上述的深刻說明)
它是如何工做的?V8維護了在最近的方法調用中做爲參數傳遞的對象類型的緩存。使用此信息能夠對未來做爲參數傳遞的對象類型作出假設。若是V8可以很好地假設將來將傳遞給方法的對象的類型,則它能夠繞開找出如何訪問對象屬性的過程,而可使用先前查找到的對象的隱藏類的存儲信息(該信息能夠經過偏移量訪問屬性)。
隱藏類和內聯緩存是如何相關聯的?當在某個對象上調用方法的時候,V8引擎會查詢對象的隱藏類去決定是否使用偏移量訪問對象的屬性。若是兩次調用了一樣的方法到一樣的隱藏類。在對相同的隱藏類成功調用同一方法兩次以後,V8會省略了隱藏類查找,只是將屬性的偏移量添加到對象指針自己。對於之後使用該方法的全部調用,V8引擎都假定隱藏類未更改,並使用之前查找中存儲的偏移量直接跳轉到特定屬性的內存地址。這大大提升了執行速度。
舉例(注:由於做者這一段說的比較模糊,因此參考了其餘文章添加了一個例子,原文中並無該示例):
function getX(o) {
return o.x;
}
複製代碼
若是是JSC(JavaScriptCore和V8同樣是JS引擎,見上文),會生成一下字節碼。
JSC還將內聯緩存嵌入到get_by_id指令中,該指令由兩個未初始化的插槽組成。其中Shape和上文提到的內聯類同樣,只是表達方式不同,它屬於SpiderMonkey。
如今假設咱們使用對象{x:'a'}調用getX。該對象的Shape具備屬性「x」,而且Shape存儲該屬性x的偏移量和屬性。首次執行該函數時,get_by_id指令查找屬性「x」並發現該值存儲在偏移量0處。
內聯緩存也是爲何同類型的對象共享隱藏類如此重要的緣由。若是你建立了兩個相同類型的對象可是卻有不一樣的隱藏類(咱們以前提到過的)。即便兩個類具備相同的類型,V8將不會使用內聯緩存,由於他們的隱藏類爲同一個屬性分配了不一樣的偏移量。
一旦Hydrogen圖被優化,Crankshaft將其下降爲較低級別的表示形式稱爲Lithium。大多數的Lithium的實現是特定結構。寄存器分配也發生在此級別。
最終,Lithium編譯爲機器碼。 而後稱之爲OSR的開始執行堆疊替換(on-stack replacement)。在咱們編譯和優化明顯長的方法的時候,會先運行它。V8不會忘記重啓優化後的代碼的時候執行會變慢。因此,它會轉換咱們全部的上下文(堆,寄存器),以便咱們能夠在執行過程當中切換到優化版本。這是一個很是複雜的任務,請記住,在全部的優化中,V8已經在最開始內聯了代碼。V8不是惟一可以作到這一點的引擎。
有一種稱爲反優化的保護措施,能夠進行相反的轉換,並在假設引擎再也不適用的狀況下還原爲未優化的代碼。
對於垃圾收集,V8使用標記清除的傳統世代方法來清理舊一代的對象。標記階段應該中止JavaScript執行。爲了控制GC成本並使執行更加穩定,V8使用了增量標記:不是遍歷整個堆而是嘗試標記每一個可能的對象,而是遍歷堆的一部分,而後恢復正常執行。下一個GC將從上一個堆遍歷中止的位置繼續。這容許在正常執行期間很是短的暫停。如前所述,清除階段由單獨的線程處理。
隨着2017年初V8 5.9的發佈,引入了新的執行管道。這個新的管道在實際的JavaScript應用程序中實現了更大的性能改進並顯著節省了內存。
新的執行管道基於Ignition,V8解釋器,TurboFan,V8最新優化的編譯器。
你能夠從V8團隊的文章中查看。
自從V8 5.9發佈以來full-codegen和Crankshaft (從2010年開發服務v8的技術)不在被v8用於Javascript的執行。 v8團隊一直在努力跟上新的JavaScript語言功能以及這些功能所需的優化。
這意味着整個V8未來將具備更加簡單和可維護的體系結構。
這些提升僅僅是開始, 新的Ignition和TurboFan管道爲進一步優化鋪平了道路,這些優化將在將來幾年內提升JavaScript性能並縮小V8在Chrome和Node.js中的佔用空間。
最後,這是有關如何編寫通過優化的JavaScript的一些技巧。你能夠從上文中輕鬆獲得這些內容,可是,爲方便起見,如下是摘要: