你不會還不瞭解v8引擎是什麼吧?

語言: CN / TW / HK

theme: condensed-night-purple highlight: atelier-cave-dark


開啟掘金成長之旅!這是我參與「掘金日新計劃 · 12 月更文挑戰」的第38天,點選檢視活動詳情

要講V8引擎,首先我們要先講一下瀏覽器,為什麼呢?因為我們最常用的瀏覽器Google的核心就是由V8引擎和WebCore組成的。

首先我們要先了解瀏覽器的核心和瀏覽器的工作原理。

瀏覽器的核心

我們經常會說:不同的瀏覽器有不同的核心組成 - Gecko:早期被Netscape和Mozilla Firefox瀏覽器瀏覽器使用。 - Trident:微軟開發,被IE4~IE11瀏覽器使用,但是Edge瀏覽器已經轉向Blink。 - ebkit:蘋果基於KHTML開發、開源的,用於Safari,Google Chrome之前也在使用。 - Blink:是Webkit的一個分支,Google開發,目前應用於Google Chrome、Edge、Opera等。等等...

事實上,我們經常說的瀏覽器核心指的是瀏覽器的排版引擎。

排版引擎(layout engine),也稱為瀏覽器引擎(browser engine)、頁面渲染引擎(rendering engine)或樣版引擎

瀏覽器的工作原理

大家有沒有深入思考過:JavaScript程式碼,在瀏覽器中是如何被執行的?

當我們在瀏覽器上輸入網址時,我們輸入的網址會經過DNS解析,解析成我們對應的伺服器Ip地址進行訪問,傳送訪問請求,伺服器接收到瀏覽器傳送的訪問請求後會返回網站的靜態資源HTML、CSS、JavaScript、圖片等資源給瀏覽器進行解析。

image.png

在我們的瀏覽器請求到網址的靜態資源解析後,都會在客戶端進行渲染。那麼渲染的過程又是如何執行的呢?

瀏覽器渲染過程

當瀏覽器接收到資源後,開始解析HTML,在瀏覽器的核心裡面有一個HTMLParser,把HTML轉換成DOM Tree(DOM樹),在這個過程中,JavaScript也可以對DOM進行操作,CSS也會被CSS Paser進行解析,轉換成styleRules(css規則),然後DOM Tree和Style Rules會結合到一起生成Render Tree(渲染樹),然很會經過Layout進行適配對不同瀏覽器的螢幕大小進行適配佈局,然後進會進行最終的繪製,生成我們最終看見的頁面模樣。

image.png

那我們的網頁中的js程式碼到底由誰來執行呢?

那就是js(V8)引擎。

那JavaScript引擎又是如何執行的呢?接下來讓我們認識JavaScript引擎

JavaScript引擎

為什麼需要JavaScript引擎呢?

我們前面說過,高階的程式語言都是需要轉成最終的機器指令來執行的。 事實上我們編寫的JavaScript無論你交給瀏覽器或者Node執行,最後都是需要被CPU執行的。但是CPU只認識自己的指令集,實際上是機器語言,才能被CPU所執行。所以我們需要JavaScript引擎幫助我們將JavaScript程式碼翻譯成CPU指令來執行。

比較常見的JavaScript引擎有哪些呢?

  • SpiderMonkey:第一款JavaScript引擎,由Brendan Eich開發(也就是JavaScript作者)。
  • Chakra:微軟開發,用於IT瀏覽器。
  • JavaScriptCore:WebKit中的JavaScript引擎,Apple公司開發。
  • V8:Google開發的強大JavaScript引擎,也幫助Chrome從眾多瀏覽器中脫穎而出。

認識完JavaScript引擎,那JavaScript引擎和瀏覽器核心是什麼關係呢?

接下來,我們就好好分析一下,JavaScript引擎和核心的關係

瀏覽器核心和JS引擎的關係

這裡我們先以WebKit為例,WebKit事實上由兩部分組成的: - WebCore:負責HTML解析、佈局、渲染等等相關的工作。 - JavaScriptCore:解析、執行JavaScript程式碼。

看到這裡,學過小程式的同學有沒有感覺非常的熟悉呢?

在小程式中編寫的JavaScript程式碼就是被JSCore執行的。

image.png

image.png

瞭解完JavaScript引擎和核心的關係,那我們來具體的瞭解一下V8引擎的原理吧!

V8引擎的原理

我們來看一下官方對V8引擎的定義:

V8是用C++編寫的Google開源高效能JavaScript和WebAssembly引擎,它用於Chrome和Node.js等。

它實現ECMAScriptWebAssembly,並在Windows 7或更高版本,macOS 10.12+和使用x64,IA-32, ARM或MIPS處理器的Linux系統上執行。V8可以獨立執行,也可以嵌入到任何C ++應用程式中

image.png

V8引擎本身的原始碼非常複雜,大概有超過100w行C++程式碼,通過了解它的架構,我們可以知道它是如何對JavaScript執行的。

Parse模組

Parse模組會將JavaScript程式碼轉換成AST(抽象語法樹),這是因為直譯器並不直接認識JavaScript程式碼。如果函式沒有被呼叫,那麼是不會被轉換成AST的。

Parse的V8官方文件:https://v8.dev/blog/scanner

Ignition

Ignition是一個直譯器,會將AST轉換成ByteCode(位元組碼) 同時會收集TurboFan優化所需要的資訊(比如函式引數的型別資訊,有了型別才能進行真實的運算)。如果函式只調用一次,Ignition會執行解釋執行ByteCode。

Ignition的V8官方文件:https://v8.dev/blog/ignition-interpreter

TurboFan

TurboFan是一個編譯器,可以將位元組碼編譯為CPU可以直接執行的機器碼。 如果一個函式被多次呼叫,那麼就會被標記為熱點函式,那麼就會經過TurboFan轉換成優化的機器碼,提高程式碼的執行效能。但是機器碼實際上也會被還原為ByteCode,這是因為如果後續執行函式的過程中,型別發生了變化(比如sum函式原來執行的是number型別,後來執行變成了string型別),之前優化的機器碼並不能正確的處理運算,就會逆向的轉換成位元組碼。

TurboFan的V8官方文件:https://v8.dev/blog/turbofan-jit

image.png

V8執行的細節

那麼我們的JavaScript原始碼是如何被解析(Parse過程)的呢?

Blink將原始碼交給V8引擎,Stream獲取到原始碼並且進行編碼轉換,Scanner會進行詞法分析(lexical analysis),詞法分析會將程式碼轉換成tokens。接下來tokens會被轉換成AST樹,經過Parser和PreParser:Parser就是直接將tokens轉成AST樹架構。

PreParser稱之為預解析,為什麼需要預解析呢?

這是因為並不是所有的JavaScript程式碼,在一開始時就會被執行。那麼對所有的JavaScript程式碼進行解析,必然會影響網頁的執行效率。所以V8引擎就實現了Lazy Parsing(延遲解析)的方案,它的作用是將不必要的函式進行預解析,也就是隻解析暫時需要的內容,而對函式的全量解析是在函式被呼叫時才會進行。比如我們在一個函式outer內部定義了另外一個函式inner,那麼inner函式就會進行預解析。生成AST樹後,會被Ignition轉成位元組碼(bytecode),之後的過程就是程式碼的執行過程。