深刻V8引擎之代碼執行機制

前言

前端工具和框架的自身更新速度很是塊,並且還不斷有新的出現。要想追遇上前端工具和框架的更新速度,你就須要抓住那些本質的知識,而後才能更加輕鬆地理解這些上層應用。好比咱們接下來要介紹的 V8 執行機制,能幫助你從底層瞭解 JavaScript,也能幫助你深刻理解語言轉換器 Babel、語法檢查工具 ESLint、前端框架 Vue 和 React 的一些底層實現機制。要深刻理解 V8 的工做原理,你須要搞清楚一些概念和原理,好比接下來咱們要詳細講解的編譯器(Compiler)、解釋器(Interpreter)、抽象語法樹(AST)、字節碼(Bytecode)、即時編譯器(JIT)等概念。前端

編譯器和解釋器

之因此存在編譯器和解釋器,是由於機器不能直接理解咱們所寫的代碼,因此在執行程序以前,須要將咱們所寫的代碼「翻譯」成機器能讀懂的機器語言。按語言的執行流程,能夠把語言劃分爲編譯型語言和解釋型語言。瀏覽器

  • 編譯型語言在程序執行以前,須要通過編譯器的編譯過程,而且編譯以後會直接保留機器能讀懂的二進制文件,這樣每次運行程序時,均可以直接運行該二進制文件,而不須要再次從新編譯了。
  • 而由解釋型語言編寫的程序,在每次運行時都須要經過解釋器對程序進行動態解釋和執行。

具體流程你能夠參考下圖: 緩存

從圖中你能夠看出這兩者的執行流程,大體可闡述爲以下:性能優化

  1. 在編譯型語言的編譯過程當中,編譯器首先會依次對源代碼進行詞法分析、語法分析,生成抽象語法樹(AST),而後是優化代碼,最後再生成處理器可以理解的機器碼。若是編譯成功,將會生成一個可執行的文件。但若是編譯過程發生了語法或者其餘的錯誤,那麼編譯器就會拋出異常,最後的二進制文件也不會生成成功。
  2. 在解釋型語言的解釋過程當中,一樣解釋器也會對源代碼進行詞法分析、語法分析,並生成抽象語法樹(AST),不過它會再基於抽象語法樹生成字節碼,最後再根據字節碼來執行程序、輸出結果。

V8 執行 JS 代碼流程

瞭解了編譯器和解釋器,接下來咱們分析 V8是如何執行一段代碼的,流程以下: 前端框架

V8 在執行過程當中既有解釋器 Ignition,又有編譯器 TurboFan,具體流程:markdown

生成抽象語法樹(AST)

高級語言是開發者能夠理解的語言,可是讓編譯器或者解釋器來理解就很是困難了。對於編譯器或者解釋器來講,它們能夠理解的就是 AST 了。因此不管你使用的是解釋型語言仍是編譯型語言,在編譯過程當中,它們都會生成一個 AST。這和渲染引擎將 HTML 格式文件轉換爲計算機能夠理解的 DOM 樹的狀況相似。網絡

生成 AST 須要通過兩個階段:架構

  • 第一階段是分詞(tokenize),又稱爲詞法分析,其做用是將一行行的源碼拆解成一個個 token。所謂 token,指的是語法上不可能再分的、最小的單個字符或字符串。
  • 第二階段是解析(parse),又稱爲語法分析,其做用是將上一步生成的 token 數據,根據語法規則轉爲 AST。若是源碼符合語法規則,這一步就會順利完成。但若是源碼存在語法錯誤,這一步就會終止,並拋出一個「語法錯誤」。

這就是 AST 的生成過程,先分詞,再解析。有了 AST 後,那接下來 V8 就會生成該段代碼的執行上下文。框架

生成字節碼

有了 AST 和執行上下文後,那接下來的第二步,解釋器 Ignition 就登場了,它會根據 AST 生成字節碼,並解釋執行字節碼。工具

字節碼就是介於 AST 和機器碼之間的一種代碼。可是與特定類型的機器碼無關,字節碼須要經過解釋器將其轉換爲機器碼後才能執行。 機器碼所佔用的空間遠遠超過了字節碼,因此使用字節碼能夠減小系統的內存使用。

執行代碼

一般,若是有一段第一次執行的字節碼,解釋器 Ignition 會逐條解釋執行。到了這裏,相信你已經發現了,解釋器 Ignition 除了負責生成字節碼以外,它還有另一個做用,就是解釋執行字節碼。在 Ignition 執行字節碼的過程當中,若是發現有熱點代碼(HotSpot),好比一段代碼被重複執行屢次,這種就稱爲熱點代碼,那麼後臺的編譯器 TurboFan 就會把該段熱點的字節碼編譯爲高效的機器碼,而後當再次執行這段被優化的代碼時,只須要執行編譯後的機器碼就能夠了,這樣就大大提高了代碼的執行效率。

字節碼配合解釋器和編譯器是最近一段時間很火的技術,好比 Java 和 Python 的虛擬機也都是基於這種技術實現的,咱們把這種技術稱爲即時編譯(JIT)。具體到 V8,就是指解釋器 Ignition 在解釋執行字節碼的同時,收集代碼信息,當它發現某一部分代碼變熱了以後,TurboFan 編譯器便閃亮登場,把熱點的字節碼轉換爲機器碼,並把轉換後的機器碼保存起來,以備下次使用。

JavaScript 的性能優化

雖然在 V8 誕生之初,也出現過一系列針對 V8 而專門優化 JavaScript 性能的方案,好比隱藏類、內聯緩存等概念都是那時候提出來的。不過隨着 V8 的架構調整,你愈來愈不須要這些微優化策略了,相反,對於優化 JavaScript 執行效率,你應該將優化的中心聚焦在單次腳本的執行時間和腳本的網絡下載上,主要關注如下三點內容:

  1. 提高單次腳本的執行速度,避免 JavaScript 的長任務霸佔主線程,這樣可使得頁面快速響應交互;
  2. 避免大的內聯腳本,由於在解析 HTML 的過程當中,解析和編譯也會佔用主線程;
  3. 減小 JavaScript 文件的容量,由於更小的文件會提高下載速度,而且佔用更低的內存。

參考文章

瀏覽器工做原理與實踐

本文使用 mdnice 排版

相關文章
相關標籤/搜索