淺析V8引擎,讓你更懂JavaScript!

導(dǎo)語(yǔ)?|?本文介紹了編譯、解釋、動(dòng)靜態(tài)語(yǔ)言等基本概念,以及V8引擎的基本流程。本文將對(duì)其進(jìn)行詳細(xì)闡述,希望為更多的開(kāi)發(fā)者提供經(jīng)驗(yàn)和幫助。
一、編譯與解釋
二進(jìn)制指令就是機(jī)器碼:
編譯:將源代碼一次性轉(zhuǎn)換成目標(biāo)代碼的過(guò)程。執(zhí)行編譯過(guò)程的程序叫編譯器(Compiler)。
解釋:將源代碼逐條轉(zhuǎn)換成目標(biāo)代碼,同時(shí)逐條運(yùn)行的過(guò)程。執(zhí)行解釋過(guò)程的程序叫解釋器(Interpreter)。解釋器一般來(lái)說(shuō)就是vm,vm有兩種,一種是基于堆棧,一種是基于寄存器。
編譯過(guò)程大致包括詞法分析、語(yǔ)法分析、語(yǔ)義分析、性能優(yōu)化、生成可執(zhí)行文件等五個(gè)步驟,期間涉及到復(fù)雜的算法和硬件架構(gòu)。解釋器與此類似。
二、靜態(tài)語(yǔ)言與動(dòng)態(tài)語(yǔ)言
高級(jí)語(yǔ)言按照?qǐng)?zhí)行方式的不同,可分為靜態(tài)語(yǔ)言和動(dòng)態(tài)語(yǔ)言。
靜態(tài)語(yǔ)言:使用編譯執(zhí)行的語(yǔ)言,如C、C++、Golang等。使用編譯器一次性生成目標(biāo)代碼,“一次編譯,無(wú)限次運(yùn)行”,程序運(yùn)行速度更快。編譯型語(yǔ)言一般是不能跨平臺(tái)的,也就是不能在不同的操作系統(tǒng)之間隨意切換。
動(dòng)態(tài)語(yǔ)言:使用解釋執(zhí)行的語(yǔ)言,如Python、Javascript、PHP等。執(zhí)行過(guò)程中需要源代碼,只要存在解釋器,源代碼可以在任何操作系統(tǒng)上運(yùn)行,可移植性好,“一次編寫(xiě),到處運(yùn)行”。

解釋型語(yǔ)言之所以能夠跨平臺(tái),是因?yàn)橛辛私忉屍鬟@個(gè)中間層。在不同的平臺(tái)下,解釋器會(huì)將相同的源代碼轉(zhuǎn)換成不同的機(jī)器碼,解釋器幫助我們屏蔽了不同平臺(tái)之間的差異。
java和C#是一種比較奇葩的存在,它們是半編譯半解釋型的語(yǔ)言,源代碼需要先轉(zhuǎn)換成一種中間文件(字節(jié)碼文件),然后再將中間文件拿到虛擬機(jī)中執(zhí)行。Java引領(lǐng)了這種風(fēng)潮,它的初衷是在跨平臺(tái)的同時(shí)兼顧執(zhí)行效率;C#是后來(lái)的跟隨者,但是C#一直止步于Windows平臺(tái),在其它平臺(tái)鮮有作為。
總結(jié):

三、V8引擎
Javascript是解釋型語(yǔ)言,那么V8引擎就對(duì)應(yīng)著解釋器。但是V8引擎為了提高JS的運(yùn)行效率,會(huì)提前編譯。
也就是V8引擎包括兩個(gè)階段:編譯、執(zhí)行,編譯階段指V8將JavaScript轉(zhuǎn)換為字節(jié)碼或者二進(jìn)制機(jī)器碼,執(zhí)行階段指解釋器解釋執(zhí)行字節(jié)碼,或者CPU直接執(zhí)行二進(jìn)制機(jī)器碼。
(一)JIT
V8引擎同時(shí)采用了解釋執(zhí)行和編譯執(zhí)行這兩種方式,也就是在運(yùn)行時(shí)進(jìn)行編譯,這種方式稱為JIT (Just in Time) 即時(shí)編譯。
V8在執(zhí)行JavaScript源碼時(shí),會(huì)先通過(guò)解析器將源碼解析成AST,解釋器會(huì)將AST轉(zhuǎn)化為字節(jié)碼,一邊解釋一遍執(zhí)行。
解釋器同時(shí)會(huì)記錄某一代碼片段的執(zhí)行次數(shù),如果執(zhí)行次數(shù)超過(guò)了某個(gè)閾值,這段代碼便會(huì)被標(biāo)記為熱代碼(Hot Code),同時(shí)將運(yùn)行信息反饋給優(yōu)化編譯器TurboFan,TurboFan根據(jù)反饋信息,會(huì)優(yōu)化并編譯字節(jié)碼,最后生成優(yōu)化的機(jī)器碼。
(二)Parser生成抽象語(yǔ)法樹(shù)
Parser生成AST抽象語(yǔ)法樹(shù)過(guò)程包括語(yǔ)法分析、詞法分析,和Babel等工具差不多。
生成AST中的一個(gè)優(yōu)化是惰性解析(Lazy Parsing),因?yàn)樵创a在執(zhí)行前如果全部完全解析的話,不僅執(zhí)行時(shí)間過(guò)長(zhǎng),而且會(huì)消耗更多的內(nèi)存。
惰性解析就是指如果遇到并不是立即執(zhí)行的函數(shù),只會(huì)對(duì)其進(jìn)行預(yù)解析(Pre-Parser),當(dāng)函數(shù)被調(diào)用時(shí),才會(huì)對(duì)其完全解析。
預(yù)解析時(shí),只會(huì)驗(yàn)證函數(shù)的語(yǔ)法是否有效、解析函數(shù)聲明以及確定函數(shù)作用域,并不會(huì)生成AST,這項(xiàng)工作由Pre-Parser預(yù)解析器完成。
(三)Ignition生成字節(jié)碼
字節(jié)碼是機(jī)器碼的抽象,可以看作是小型的構(gòu)建塊。相比機(jī)器碼,字節(jié)碼不僅占用內(nèi)存少,而且生成字節(jié)碼的時(shí)間很快,提升了啟動(dòng)速度。
另外,字節(jié)碼與特定類型的機(jī)器碼無(wú)關(guān),通過(guò)解釋器將字節(jié)碼轉(zhuǎn)換為機(jī)器碼后才可以執(zhí)行,這樣也使得V8更加方便的移植到不同的CPU架構(gòu)。
可以通過(guò)如下命令,查看JavaScript代碼生成的字節(jié)碼。
node --print-bytecode index.js注意,解釋器執(zhí)行字節(jié)碼前,還是會(huì)將字節(jié)碼轉(zhuǎn)為機(jī)器碼,因?yàn)橛?jì)算機(jī)只識(shí)別機(jī)器碼。
(四)TurboFan
Ignition執(zhí)行上一步生成的字節(jié)碼,并記錄代碼運(yùn)行的次數(shù)等信息,如果同一段代碼執(zhí)行了很多次,就會(huì)被標(biāo)記為 “HotSpot”(熱點(diǎn)代碼),然后把這段代碼發(fā)送給 編譯器TurboFan。
然后TurboFan把它編譯為更高效的機(jī)器碼儲(chǔ)存起來(lái),等到下次再執(zhí)行到這段代碼時(shí),就會(huì)用現(xiàn)在的機(jī)器碼替換原來(lái)的字節(jié)碼進(jìn)行執(zhí)行,這樣大大提升了代碼的執(zhí)行效率。
另外,當(dāng)TurboFan判斷一段代碼不再為熱點(diǎn)代碼的時(shí)候,會(huì)執(zhí)行去優(yōu)化的過(guò)程,把優(yōu)化的機(jī)器碼丟掉,然后執(zhí)行過(guò)程回到Ignition。
TurboFan做的優(yōu)化包括內(nèi)聯(lián)(inlining)和逃逸分析(Escape Analysis)。
內(nèi)聯(lián)就是將相關(guān)聯(lián)的函數(shù)進(jìn)行合并,減少運(yùn)行時(shí)間。比如:
function add(a, b) {return a + b}function foo() {return add(2, 4)}
內(nèi)聯(lián)處理后:
function fooAddInlined() {var a = 2var b = 4var addReturnValue = a + breturn addReturnValue}// 因?yàn)?fooAddInlined 中 a 和 b 的值都是確定的,所以可以進(jìn)一步優(yōu)化function fooAddInlined() {return 6}
逃逸分析就是分析對(duì)象的生命周期是否僅限于當(dāng)前函數(shù),如果是的話會(huì)對(duì)其進(jìn)行優(yōu)化。比如:
function add(a, b){const obj = { x: a, y: b }return obj.x + obj.y}
會(huì)處理成:
function add(a, b){const obj_x = aconst obj_y = breturn obj_x + obj_y}
四、總體流程

參考資料:
1.v8
2.編譯型語(yǔ)言和解釋型語(yǔ)言的區(qū)別
3.編譯器與解釋器的區(qū)別
4.js引擎能做到多小
5.深入理解JS引擎
6.V8是如何執(zhí)行JavaScript代碼的
7.JIT為什么能大幅度提升性能
8.JIT(just-in-time)即時(shí)編譯
?作者簡(jiǎn)介
楊國(guó)旺
騰訊前端開(kāi)發(fā)工程師
騰訊前端開(kāi)發(fā)工程師,歡迎討論前端問(wèn)題。
?推薦閱讀
手把手教你從0開(kāi)始實(shí)現(xiàn)C++協(xié)程!
閱見(jiàn)深我,讀享生活,TVP讀書(shū)分享會(huì)帶你解鎖新知!


