很多人都知道v8引擎,v8引擎是一種js引擎的實(shí)現(xiàn)。在開始介紹v8之前,先搞清JavaScript引擎是什么,這里簡單引用
JavaScript引擎是執(zhí)行JavaScript代碼的程序或解釋器。javaScript引擎可以實(shí)現(xiàn)為標(biāo)準(zhǔn)解釋器或即時(shí)編譯器,它以某種形式將JavaScript編譯為字節(jié)碼。
那么除了v8引擎,你還知道那些js引擎
V8 - 開源,由Google開發(fā),用C ++編寫
Rhin- 由Mozilla基金會(huì)開源,完全用Java開發(fā)
SpiderMonkey 第一個(gè)JavaScript引擎,Netscape Navigator,F(xiàn)irefox
JavaScriptCore 蘋果公司為Safari開發(fā)
KJS 最初由Harri Porten為KDE項(xiàng)目的Konqueror網(wǎng)絡(luò)瀏覽器開發(fā)
Chakra** (JScript9) Microsoft Edge
Chakra** (JavaScript) Microsoft IE9-IE11
Nashorn 作為OpenJDK的一部分,由Oracle Java語言和工具組編寫
JerryScript 一個(gè)物聯(lián)網(wǎng)的輕量級引擎

V8是被設(shè)計(jì)用來提高網(wǎng)頁瀏覽器內(nèi)部JavaScript執(zhí)行的性能,那么如何提高性能呢?
為了提高性能,v8會(huì)把js代碼轉(zhuǎn)換為高效的機(jī)器碼,而不在是依賴于解釋器去執(zhí)行。v8引入了
JIT在運(yùn)行時(shí)把js代碼進(jìn)行轉(zhuǎn)換為機(jī)器碼。這里的主要區(qū)別在于V8不生成字節(jié)碼或任何中間代碼。
v8曾經(jīng)有兩個(gè)編譯器(v5.9之前)
full-codegen?— 一個(gè)簡單且速度非??斓木幾g器,可以生成簡單且相對較慢的機(jī)器碼
Crankshaft?—? 一個(gè)更復(fù)雜的(Just-In-Time)優(yōu)化編譯器,生成高度優(yōu)化的代碼
v8充分多進(jìn)程,主進(jìn)程負(fù)責(zé)獲取代碼,編譯生成機(jī)器碼,有專門負(fù)責(zé)優(yōu)化的進(jìn)程,,還有一個(gè)監(jiān)控進(jìn)程負(fù)責(zé)分析那些代碼執(zhí)行比較慢,以遍Crankshaft 做優(yōu)化,最后還有一個(gè)就是GC進(jìn)程,負(fù)責(zé)內(nèi)存垃圾回收。
v8的具體優(yōu)化方案:
第一個(gè)優(yōu)化就是盡可能最大的內(nèi)聯(lián)。

內(nèi)聯(lián)主要是通過優(yōu)化被調(diào)用函數(shù)的調(diào)用棧。這塊理解還不是很透徹。
第二個(gè)優(yōu)化就隱藏類。
js中并沒有指針的概念,js的對象訪問就是基于隱藏類的。下面理解下隱藏類
function Point(x, y) {
this.x = x;
this.y = y;
}
var p1 = new Point(1, 2);
執(zhí)行new Point(1, 2),v8會(huì)為其生成一個(gè)隱藏類C0

C0現(xiàn)在是一個(gè)空的,沒有屬性。
執(zhí)行完this.x = x
v8會(huì)基于C0,創(chuàng)建隱藏類C1

C1”描述了可以找到屬性x的存儲器中的位置(類似指針)
執(zhí)行完this.y = y

V8會(huì)創(chuàng)建一個(gè)隱藏類C2,C1則會(huì)增加一個(gè)類轉(zhuǎn)換,表示隱藏類切換為C2
隱藏類轉(zhuǎn)換取決于將屬性添加到對象的順序.----非常重要
下面代碼中p1,p2的屬性添加順序不一樣,結(jié)果就是p1,p2會(huì)有兩個(gè)不同的隱藏類。這種情況下還是最好采用相同的初始化順序,以便系統(tǒng)可以復(fù)用隱藏類,幫助系統(tǒng)提升性能。
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;
第三個(gè)是內(nèi)聯(lián)緩存
內(nèi)聯(lián)緩存依賴于對相同方法的重復(fù)調(diào)用傾向于在相同類型的對象上發(fā)生的觀察。
V8維護(hù)一個(gè)在最近的方法調(diào)用中作為參數(shù)傳遞的對象類型的緩存,并使用這些信息來預(yù)測將來作為參數(shù)傳遞的對象的類型。如果V8能夠?qū)鬟f給方法的對象的類型做出一個(gè)很好的假設(shè),那么它可以繞過確定如何訪問對象屬性的過程,而是使用以前查找存儲的信息到對象的隱藏類。
V8在執(zhí)行一個(gè)對象的方法時(shí),會(huì)通過查找隱藏類確定偏移量,然后去執(zhí)行,在兩次成功調(diào)用后,V8會(huì)假定對象不變,再次調(diào)用時(shí)直接利用偏移量去訪問,而不在去做隱藏類查找。這極大的提升了運(yùn)行速度,這也是為什么在初始化對象時(shí),最好保證同樣的順序。
如果兩個(gè)對象使用不同的隱藏類,內(nèi)聯(lián)緩存則無法應(yīng)用。如下圖

第四 Compilation to machine code
V8之前的優(yōu)化,是基于某種假設(shè)。如果假設(shè)失敗,v8會(huì)啟動(dòng)一種為 deoptimization 的行為,重新去分析。這種行為的成本很高,所以要盡量避免。
第五,垃圾回收機(jī)制
對于垃圾收集,V8采用了傳統(tǒng)的分代式掃描方式來清理老一代。標(biāo)記階段應(yīng)該停止JavaScript的執(zhí)行。為了控制GC成本并使執(zhí)行更加穩(wěn)定,V8使用了漸進(jìn)式標(biāo)記:而不是走遍整個(gè)堆,試圖標(biāo)記每一個(gè)可能的對象,它只走一部分堆,然后恢復(fù)正常執(zhí)行。
下一個(gè)GC停止將從先前堆走過的地方繼續(xù)。這允許在正常執(zhí)行期間非常短的暫停。如前所述,掃描階段由不同的線程處理。
最后看下最新的編譯器TurboFan
隨著2017年早些時(shí)候V8 5.9的發(fā)布,一個(gè)新的執(zhí)行流程被引入。這個(gè)新的管道在實(shí)際的JavaScript應(yīng)用程序中實(shí)現(xiàn)了更大的性能改進(jìn)和顯著的內(nèi)存節(jié)省。

可以看出性能有大幅提升
了解V8引擎的內(nèi)部運(yùn)作原理后,如何編寫優(yōu)化的JavaScript呢?
總是以相同的順序?qū)嵗瘜ο髮傩?,以便可以共享隱藏類和隨后優(yōu)化的代碼。
2.在實(shí)例化之后向?qū)ο筇砑訉傩詫?qiáng)制隱藏類更改,并放慢為以前隱藏類優(yōu)化的所有方法。相反,在其構(gòu)造函數(shù)中分配所有對象的屬性重復(fù)執(zhí)行相同方法的代碼將比僅執(zhí)行一次(由于內(nèi)聯(lián)高速緩存)執(zhí)行許多不同方法的代碼運(yùn)行得更快。
4.避免鍵數(shù)不是增量數(shù)的稀疏數(shù)組。稀疏數(shù)組中沒有每個(gè)元素都是哈希表。這種陣列中的元素訪問費(fèi)用較高。另外,盡量避免預(yù)分配大型數(shù)組。隨著你的成長,你的成長會(huì)更好。最后,不要?jiǎng)h除數(shù)組中的元素。
5.v8用32位的對象和數(shù)字。由于它的31位,它使用了一點(diǎn)來知道它是一個(gè)對象(flag = 1)還是一個(gè)稱為SMI(SMall Integer)的整數(shù)(flag = 0)。然后,如果一個(gè)數(shù)字值大于31位,V8會(huì)把這個(gè)數(shù)字裝箱,把它變成一個(gè)double,并創(chuàng)建一個(gè)新的對象,把數(shù)字放在里面。盡可能使用31位有符號數(shù)字,以避免昂貴的拳擊操作成JS對象