V8在編譯JS的過程當中,並不會將全部的JS解析成中間代碼,主要緣由以下:瀏覽器
基於以上緣由,因此JS虛擬機實現了惰性解析。惰性解析就是解析器在解析的過程當中,若是遇到函數聲明,就會跳過函數內部的代碼,僅僅生成頂層代碼的AST和字節碼。其中閉包會讓V8的解析過程變的複雜。緩存
function foo() {
let a = 1
return function bar(b) {
return a + b
}
}
複製代碼
當函數引用閉包時,函數執行完成,會從棧中彈出,函數中的變量和做用域會被銷燬,可是閉包中引用的父函數變量會依然保存到內存中,沒有被銷燬掉。處理這個任務的模塊叫預解析器。markdown
V8引入預解析器,當解析頂層代碼的時候,若是遇到一個函數,預解析器並不會直接跳過該函數,而是對該函數作一次快速的預解析。閉包
V8的惰性解析是指解析器在解析過程當中,若是遇到函數聲明,則跳過函數內部代碼,僅生成頂層代碼的AST和字節碼。利用惰性解析能夠加速JS的編譯速度和節約內存。架構
V8解析函數的時候,會使用預解析器快速解析函數內部是否包含了外部函數聲明的變量,若是引用了,就會將該變量複製存放到堆中,即便當前函數執行完也不會釋放該變量,從而實現閉包的功能。函數
字節碼就是編譯過程當中的中間代碼,是機器代碼的抽象,V8中的字節碼有兩個做用:優化
在瀏覽器執行JS以前,早期的V8會先編譯JS爲未優化過的二進制機器代碼,而後執行二進制代碼。若是瀏覽器再次打開相同的頁面,並且JS沒有修改過,此時再編譯就會浪費CPU的資源,因此瀏覽器引入二進制代碼緩存,把編譯後的二進制代碼緩存在內存中,省去編譯的時間。spa
採用緩存是典型的以空間換時間的策略,犧牲存儲空間換取執行速度。JS編譯成二進制後,二進制文件十分佔內存,V8爲了提高啓動速度,採用惰性編譯,這樣能解決部份內存佔用的問題。可是若是函數中有較多的閉包,會致使閉包中的代碼沒法緩存。因此只緩存頂層代碼是不完美的,因此V8引入字節碼來繼續下降V8執行時的內存佔用。3d
上圖看出,字節碼的佔用空間遠小於二進制代碼,因此能夠針對字節碼來進行操做優化。同時瀏覽器緩存全部的字節碼。雖然字節碼的執行速度稍慢機器碼,可是採用字節碼能有效下降內存,並提升代碼啓動速度。code
JS代碼啓動流程圖:
解釋器能夠快速生成字節碼,優化編譯器雖然須要更長的時間來處理,可是最終會產生更高效的機器碼,這就是V8在使用的模型,它的解釋器叫Ignition,是全部引擎中最快的解釋器。V8的優化編譯器叫TurboFan,由它生成高度優化的機器碼。
引入字節碼,能夠統一將字節碼轉換爲不一樣平臺的二進制代碼(機器碼),不用再去針對不一樣的CPU去作兼容。並且字節碼的執行過程跟CUP執行二進制代碼相似,也下降了轉換底層代碼的工做量。
早期的V8在啓動時,直接將JS源碼編譯爲二進制的機器碼,可是會產生兩個問題:
爲了解決上述問題,V8進行了重構,引入了中間的字節碼。字節碼優點有三點: