12.02 一段JavaScript代碼放在V8引擎當中究竟是如何執行的呢?

神三元:https://juejin.im/post/5dd8b3a851882572f56b578f?utm_source=bigezhang.com#heading-6


一段JavaScript代碼放在V8引擎當中究竟是如何執行的呢?

首先需要明白的是,機器是讀不懂 JS 代碼,機器只能理解特定的機器碼,那如果要讓 JS 的邏輯在機器上運行起來,就必須將 JS 的代碼翻譯成機器碼,然後讓機器識別。JS屬於解釋型語言,對於解釋型的語言說,解釋器會對源代碼做如下分析:

1.通過詞法分析和語法分析生成 AST(抽象語法樹)

2.生成字節碼

然後解釋器根據字節碼來執行程序。但 JS 整個執行的過程其實會比這個更加複雜,接下來就來一一地拆解。

1.生成 AST

生成 AST 分為兩步——詞法分析和語法分析。

詞法分析即分詞,它的工作就是將一行行的代碼分解成一個個token。 比如下面一行代碼:

let name = 'sanyuan'

其中會把句子分解成四個部分:


一段JavaScript代碼放在V8引擎當中究竟是如何執行的呢?


即解析成了四個token,這就是詞法分析的作用。

接下來語法分析階段,將生成的這些 token 數據,根據一定的語法規則轉化為AST。舉個例子:

let name = 'sanyuan'
console.log(name)

最後生成的 AST 是這樣的:


一段JavaScript代碼放在V8引擎當中究竟是如何執行的呢?


當生成了 AST 之後,編譯器/解釋器後續的工作都要依靠 AST 而不是源代碼。順便補充一句,babel 的工作原理就是將 ES6 的代碼解析生成ES6的AST,然後將 ES6 的 AST 轉換為 ES5 的AST,最後才將 ES5 的 AST 轉化為具體的 ES5 代碼。

回到 V8 本身,生成 AST 後,接下來會生成執行上下文

2. 生成字節碼

開頭就已經提到過了,生成 AST 之後,直接通過 V8 的解釋器(也叫Ignition)來生成字節碼。但是字節碼並不能讓機器直接運行,那你可能就會說了,不能執行還轉成字節碼幹嘛,直接把 AST 轉換成機器碼不就得了,讓機器直接執行。確實,在 V8 的早期是這麼做的,但後來因為機器碼的體積太大,引發了嚴重的內存佔用問題。

給一張對比圖讓大家直觀地感受以下三者代碼量的差異:


一段JavaScript代碼放在V8引擎當中究竟是如何執行的呢?


很容易得出,字節碼是比機器碼輕量得多的代碼。那 V8 為什麼要使用字節碼,字節碼到底是個什麼東西?

子節碼是介於AST 和 機器碼之間的一種代碼,但是與特定類型的機器碼無關,字節碼需要通過解釋器將其轉換為機器碼然後執行。

字節碼仍然需要轉換為機器碼,但和原來不同的是,現在不用一次性將全部的字節碼都轉換成機器碼,而是通過解釋器來

逐行執行字節碼,省去了生成二進制文件的操作,這樣就大大降低了內存的壓力。

3. 執行代碼

接下來,就進入到字節碼解釋執行的階段啦!

在執行字節碼的過程中,如果發現某一部分代碼重複出現,那麼 V8 將它記做熱點代碼(HotSpot),然後將這麼代碼編譯成機器碼保存起來,這個用來編譯的工具就是V8的編譯器(也叫做TurboFan) , 因此在這樣的機制下,代碼執行的時間越久,那麼執行效率會越來越高,因為有越來越多的字節碼被標記為熱點代碼,遇到它們時直接執行相應的機器碼,不用再次將轉換為機器碼。

其實當你聽到有人說 JS 就是一門解釋器語言的時候,其實這個說法是有問題的。因為字節碼不僅配合瞭解釋器,而且還和編譯器打交道,所以 JS 並不是完全的解釋型語言。而編譯器和解釋器的 根本區別在於前者會編譯生成二進制文件但後者不會。

並且,這種字節碼跟編譯器和解釋器結合的技術,我們稱之為即時編譯, 也就是我們經常聽到的JIT。

這就是 V8 中執行一段JS代碼的整個過程,梳理一下:

  1. 首先通過詞法分析和語法分析生成 AST
  2. 將 AST 轉換為字節碼
  3. 由解釋器逐行執行字節碼,遇到熱點代碼啟動編譯器進行編譯,生成對應的機器碼, 以優化執行效率。


分享到:


相關文章: