做爲一個前端程序員,天天上班的第一件事就是打開電腦,不禁自主的點開chrome
瀏覽器,或是摸會兒魚或是立馬進入工做狀態。接下來瀏覽器窗口就會陪伴着你度過一天的時光,正常到七八點鐘,晚點就九十點鐘,再晚點就陪你跨過一天,時刻關注着你的工做。做爲一個忠誠陪伴你的夥伴,你捫心自問,你有認真的瞭解過它是如何工做的嗎?你有走進過它的心裏世界嗎?前端
若是你也好奇過,那麼請收看這期的《走進chrome心裏,瞭解V8引擎是如何工做的》。程序員
在深刻了解一件事物以前,首先要知道它是什麼。chrome
V8
是一個由Google
開源的採用C++
編寫的高性能JavaScript
和WebAssembly
引擎,應用在 Chrome
和Node.js
等中。它實現了ECMAScript
和WebAssembly
,運行在Windows 7
及以上、macOS 10.12+
以及使用x6四、IA-3二、ARM
或MIPS
處理器的Linux
系統上。 V8
能夠獨立運行,也能夠嵌入到任何C++
應用程序中。segmentfault
接下來咱們來關心關心它如何誕生的,以及爲何叫這個名字。瀏覽器
V8最初是由Lars Bak
團隊開發的,以汽車的V8
發動機(有八個氣缸的V型發動機)進行命名,預示着這將是一款性能極高的JavaScript
引擎,在2008年9月2號
同chrome
一同開源發佈。架構
咱們寫的JavaScript
代碼最終是要在機器中被執行的,但機器沒法直接識別這些高級語言。須要通過一系列的處理,將高級語言轉換成機器能夠識別的的指令,也就是二進制碼,交給機器執行。這中間的轉換過程就是V8
的具體工做。ide
接下來咱們就來詳細的瞭解一下。函數
首先來看一下V8
的內部組成。V8
的內部有不少模塊,其中最重要的4個以下:性能
AST
AST
轉換成字節碼並執行,同時會標記熱點代碼如下是V8
中幾個重要模塊的具體工做流程圖。咱們逐個分析。優化
Parser解析器負責將源代碼轉換成抽象語法樹AST
。在轉換過程當中有兩個重要的階段:詞法分析(Lexical Analysis)
和語法分析(Syntax Analysis)
。
也稱爲分詞,是將字符串形式的代碼轉換爲標記(token)序列的過程。這裏的token
是一個字符串,是構成源代碼的最小單位,相似於英語中單詞。詞法分析也能夠理解成將英文字母組合成單詞的過程。詞法分析過程當中不會關心單詞之間的關係。好比:詞法分析過程當中可以將括號標記成token
,但並不會校驗括號是否匹配。
JavaScript
中的token
主要包含如下幾種:
關鍵字:var、let、const等
標識符:沒有被引號括起來的連續字符,多是一個變量,也多是 if、else 這些關鍵字,又或者是 true、false 這些內置常量
運算符: +、-、 *、/ 等
數字:像十六進制,十進制,八進制以及科學表達式等
字符串:變量的值等
空格:連續的空格,換行,縮進等
註釋:行註釋或塊註釋都是一個不可拆分的最小語法單元
標點:大括號、小括號、分號、冒號等
如下是const a = 'hello world'
通過esprima
詞法分析後生成的tokens
。
[ { "type": "Keyword", "value": "const" }, { "type": "Identifier", "value": "a" }, { "type": "Punctuator", "value": "=" }, { "type": "String", "value": "'hello world'" } ]
語法分心是將詞法分析產生的token
按照某種給定的形式文法轉換成AST
的過程。也就是把單詞組合成句子的過程。在轉換過程當中會驗證語法,語法若是有錯的話,會拋出語法錯誤。
上述const a = 'hello world'
通過語法分析後生成的AST
以下:
{ "type": "Program", "body": [ { "type": "VariableDeclaration", "declarations": [ { "type": "VariableDeclarator", "id": { "type": "Identifier", "name": "a" }, "init": { "type": "Literal", "value": "hello world", "raw": "'hello world'" } } ], "kind": "const" } ], "sourceType": "script" }
通過Parser
解析器生成的AST
將交由Ignition
解釋器進行處理。
Ignition解釋器負責將AST
轉換成字節碼(Bytecode)並執行。字節碼是介於AST
和機器碼之間的一種代碼,與特定類型的機器代碼無關,須要經過解釋器轉換成機器碼才能夠執行。
看到這裏想必你們都有疑惑,既然字節碼也須要轉換成機器碼才能運行,那一開始爲何不直接將AST
轉換成機器碼直接運行呢?轉換成機器碼直接運行速度確定更快,那爲何還要加一箇中間過程呢?
其實在V8
的5.9
版本以前是沒有字節碼的,而是直接將JS代碼編譯成機器碼並將機器碼存儲到內存中,這樣就佔用了大量的內存,而早期的手機內存都不高,過分的佔用會致使手機性能大大的降低;並且直接編譯成機器碼致使編譯時間長,啓動速度慢;再者直接將JS代碼轉換成機器碼須要針對不一樣的CPU
架構編寫不一樣的指令集,複雜度很高。
5.9
版本之後引入了字節碼,能夠解決上述內存佔用大、啓動時間長、代碼複雜度高這幾個問題。
接下來咱們來看看Ignition
是如何將AST
轉換成字節碼的。
下圖是Ignition
解釋器的工做流程圖。AST
須要先經過字節碼生成器,再通過一系列的優化以後才能生成字節碼。
其中的優化包括:
將代碼轉換成字節碼後就能夠經過解釋器執行了。Ignition
在執行的過程當中,會監視代碼的執行狀況並記錄執行信息,如函數的執行次數、每次執行函數時所傳的參數等。
當同一段代碼被執行屢次,就會被標記成熱點代碼。熱點代碼會交給TurboFan
編譯器進行處理。
TurboFan
拿到Ignition
標記的熱點代碼後,會先進行優化處理,而後將優化後字節碼編譯成更高效的機器碼存儲起來。下次再次執行相同代碼時,會直接執行相應的機器碼,這樣就在很大程度上提高了代碼的執行效率。
當一段代碼再也不是熱點代碼後,TurboFan
會進行去優化的過程,將優化編譯後的機器碼還原成字節碼,將代碼的執行權利交還給Ignition
。
如今咱們來看一看具體的執行過程。
以sum += arr[i]
爲例,因爲JS
是動態類型的語言,每次的sum
和arr[i]
都有多是不一樣的類型,在執行這段代碼時,Ignition
每次都會檢查sum
和arr[i]
的數據類型。當發現一樣的代碼被執行了屢次時,就將其標記爲熱點代碼,交給TurboFan
。
TurboFan
在執行時,若是每次都判斷sum
和arr[i]
的數據類型是很浪費時間的。所以在優化時,會根據以前的幾回執行肯定sum
和arr[i]
的數據類型,將其編譯成機器碼。下次再執行時,省去了判斷數據類型的過程。
但若是在後續的執行過程當中,arr[i]
的數據類型發生了改變,以前生成的機器碼就不知足要求了,TurboFan
會把以前生成的機器碼丟棄,將執行權利再交給Ignition
,完成去優化的過程。
熱點代碼:
優化前:
優化後:
如今咱們來總結一下V8
的執行過程:
Parser
解析器,通過詞法分析和語法分析生成AST
AST
通過Ignition
解釋器生成字節碼並執行TurboFan
編譯器生成機器碼並執行這種字節碼與解釋器和編譯器結合的技術,就是咱們一般所說的即時編譯(JIT
)。
本文並無介紹垃圾回收器Orinoco
,V8
的垃圾回收機制能夠單獨用一篇文章來詳細介紹,咱們下期再見。