瀏覽器的引擎編譯、執行原理知多少?
本文轉載自微信公眾號「前端萬有引力」,作者一川 。轉載本文請聯系前端萬有引力公眾號。
1寫在前面
瀏覽器引擎是如何編譯、執行JS代碼的呢?
本文將分析瀏覽器引起對JS代碼的編譯情況,并結合實際開發經驗,重新理解底層的編譯解析機制,將有助于理解前端開發在跨端中的應用以及一套代碼生成多端的底層邏輯。那么:
- Javascript代碼被執行分為幾個階段呢?
- AST到底是做什么用的?
2V8引擎
我們知道程序語言分為編譯型語言和解釋型語言,他們各自的特點是:
- 編譯型語言:在代碼執行前編譯器直接將對應的代碼轉換為機器碼。如C++
- 解釋型語言:先將代碼轉換為編譯型代碼,再轉為機器碼,其是在運行時轉換的。如:Python、Javascript
為了提高運行效率,很多瀏覽器廠商在不斷努力,在現代瀏覽器中Chrome的v8引擎是最出類拔萃的,引入了Java虛擬機和C++編譯器的眾多技術。也正因此,Node.js也是基于V8引擎開發的。
那么v8引擎執行JS代碼需要經歷哪些階段,如下:
- Parse階段:v8引擎負責將JS代碼轉換為AST(抽象語法樹)
- Ignition階段:解釋器將AST轉換為字節碼,解析執行字節碼,同時為下階段優化編譯提供需要的信息
- TurboFan階段:編譯器利用上個階段收集的信息,將字節碼優化為可以執行的機器碼
- Orinoco階段:垃圾回收階段,將程序中不再使用的內存空間進行回收
編譯、執行流程圖
AST
在計算機科學中,抽象語法樹(abstract syntax tree 或者縮寫為 AST),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式,這里特指編程語言的源代碼。
在開發生產中,我們經常使用eslint和babel這些工具都和AST有聯系,v8引擎就是通過編譯器將源代碼解析為AST的。常見的應用場景有:
- JS反編譯,語法解析
- Babel編譯ES6語法
- 代碼高亮
- 關鍵字匹配
- 代碼壓縮
生成AST有兩個關鍵:詞法分析和語法分析 語法分析:這個階段會將源代碼拆分成最小的、不可再分的詞法單元,稱為token,代碼中的空格在JS中是直接忽略的。詞法單元之間都是獨立的,也即在該階段我們并不關心每一行代碼是通過什么方式組合在一起的。
語法分析:這個過程是將詞法單元轉換成一個由元素逐級嵌套所組成的待料了程序語法結構的樹,被稱為抽象語法樹。將上一階段生成的 token 列表轉換為如下圖右側所示的 AST,根據這個數據結構大致可以看出轉換之前源代碼的基本構造。
簡而言之,詞法分析階段就是將代碼拆解成獨立的、不可再分的tokens,而語法分析階段就是將拆分后的tokens進行解析,根據其在整個代碼上下文中的作用和聯系,去除多余的token形成抽象語法樹。
瀏覽器還不支持es6語法,需要將其轉換為es5語法,這個過程需要借助babel來實現。將es6源碼解析成AST,再將es6語法的抽象語法樹轉為es5的抽象語法樹,最后利用它來生成es5的源代碼。
生成字節碼
Ignition階段就是將AST轉換為字節碼,但是之前的v8版本不會經歷此過程,最早只是直接通過AST轉成機器碼,后面的版本才開始對其進行改進。將AST直接轉為機器碼是存在問題的,因為:
- 直接轉換會帶來內存占用過大的問題。因為將抽象語法樹全部生成了機器碼,而機器碼相比字節碼占用的內存更多
- 某些JS使用場景使用解釋器更為合適。解析成字節碼,有些代碼沒必要解析成機器碼,進而可以減少占用大量的內存空間
V8引擎重新引進Ignition解釋器,將抽象語法樹轉換成字節碼后,內存占用顯著降低,同時可以使用JIT編譯器做進一步優化。
字節碼是介于AST和機器碼之間的代碼,需要將其轉換為機器碼后才能執行,字節碼可以理解為機器碼的一種抽象。
解釋器在得到AST后,會按需進行解釋和執行,也就是說如果某個函數沒有被調用,則不會去解釋執行它。
解釋器創建了調用棧來記錄函數的調用流程,每調用一個函數,解釋器就會把該函數添加進調用棧。解釋器會為被添加進入的函數創建一個棧幀,這個棧幀是用來保存函數的局部變量以及執行語句,因此會立即執行這個棧幀。如果正在執行的函數還調用了其他函數,那么新函數也將會被添加進調用棧并執行,一旦這個函數執行結束,對應的棧幀就會被立即銷毀。查看調用棧的方式有兩種:調用函數console.log()打印到控制臺,利用瀏覽器開發者工具進行斷點調試。
生成機器碼
如果發現一段代碼被重復執行多次的情況,生成的字節碼以及分析數據會傳給TurboFan編譯器,它會根據分析數據的情況生成優化好的機器碼。
TurboFan編譯器是JIT優化的編譯器,TurboFan的編譯線程和生成字節碼不會在同一個線程上,這樣可以和Ignition解釋器相互配合著使用,不受另外一方的影響。由Ignition解釋器收集的分析數據被TurboFan編譯器使用,主要是通過一種推測優化的技術,生成已經優化的機器碼進行執行。
優化后的機器碼作用與緩存很類似,當解釋器再次遇到相同的內容時,就可以直接執行優化后的機器碼。當然優化后的代碼有可能會無法運行(比如函數參數類型改變),那么會再次反優化為字節碼交給解釋器。
3參考文章
《Javascript核心原理精講》
《前端也要懂編譯:AST 從入門到上手指南》
《編譯原理》
4寫在最后
當前市面上比較主流的JS引擎編譯過程大部分類似,主要原因可能是在某些地方加入了特定的優化,但是其核心思路和v8大體差不多。AST是比較重要的知識點,深入了解之后有助于自己實現前端工具。對此可以通過多研究一些前端工具,來提升自己的業務開發效率和編程能力。