詳解JavaScript引擎的相關(guān)概念和工作原理
譯文
【51CTO.com快譯】朋友,您在編譯和執(zhí)行代碼時(shí),是否曾考慮過(guò)JavaScript背后的引擎?作為一名程序員,我建議您通過(guò)本文來(lái)了解JavaScript引擎的工作原理。這將有益于您編寫(xiě)出更加流暢且高質(zhì)量的JavaScript代碼。
什么是JavaScript引擎?
JavaScript引擎遵循的是ECMAScript標(biāo)準(zhǔn)。此類(lèi)標(biāo)準(zhǔn)定義了JavaScript引擎的工作機(jī)制和所有功能。因此,JavaScript引擎是一個(gè)程序,它可以幫助您將JavaScript代碼轉(zhuǎn)換為較低級(jí)別的機(jī)器代碼。
總的說(shuō)來(lái),諸如JavaScript和FORTRAN等高級(jí)語(yǔ)言都是從機(jī)器語(yǔ)言中抽象出來(lái)的。與C或C++相比,JavaScript的抽象程度更高。而C和C++更接近于硬件,因此運(yùn)行效率也會(huì)更高一些。
編譯(compilation)與解釋(interpretation)
編程語(yǔ)言通常通過(guò)編譯(compilation)和解釋(interpretation)來(lái)實(shí)現(xiàn)代碼的功能。其中,
- 編譯器可以被定義為協(xié)助轉(zhuǎn)換代碼的程序。它可以將由任何編程語(yǔ)言(源語(yǔ)言)所編寫(xiě)的代碼,轉(zhuǎn)換為另一種目標(biāo)語(yǔ)言的代碼。例如:將源代碼從高級(jí)編程語(yǔ)言轉(zhuǎn)換為低級(jí)編程語(yǔ)言(即機(jī)器語(yǔ)言),來(lái)執(zhí)行既定的任務(wù)。
- 而解釋器則是通過(guò)逐行、逐條指令地分析源代碼,在無(wú)需第三方參與的情況下,直接在目標(biāo)機(jī)器上執(zhí)行相應(yīng)機(jī)器代碼。
盡管編譯和解釋是完成編程語(yǔ)言的兩個(gè)方面,但是由于使用解釋的大多數(shù)系統(tǒng)都需要由編譯器完成的翻譯工作,因此在某些情況下兩者之間是存在交集的。就JavaScript而言,其技術(shù)上的編譯,也屬于解釋類(lèi)別。也就是說(shuō),JavaScript編譯器在運(yùn)行的時(shí)候,其執(zhí)行的就是Just-In-Time(JIT)編譯。在此,JavaScript引擎恰好可以連接到瀏覽器和Node JS之類(lèi)的Web服務(wù)器,以便用戶(hù)具有運(yùn)行時(shí)(run-time)編譯、以及執(zhí)行JavaScript代碼的權(quán)限。
JavaScript引擎剖析
ECMA腳本通過(guò)指定瀏覽器實(shí)施JavaScript的過(guò)程,以便程序在每個(gè)單一瀏覽器中都能運(yùn)行一致。當(dāng)然,每個(gè)瀏覽器都為提交過(guò)的JavaScript代碼提供了一個(gè)運(yùn)行所需的JavaScript引擎。例如:Netscape瀏覽器使用的是Spider Monkey JavaScript引擎。該引擎被定義為帶有零優(yōu)化(zero optimizations)的基本解釋器。它雖然可以運(yùn)行提交過(guò)來(lái)的JavaScript代碼,但是耗時(shí)較長(zhǎng)。
工作原理
JavaScript引擎工作的基本工作流程是:獲取JavaScript源代碼,然后使用易于CPU理解的二進(jìn)制指令(機(jī)器代碼)進(jìn)行編譯。
JavaScript引擎主要由基線編譯器組成,該編譯器以中間表示(intermediate representation,IR)的形式編譯代碼。換言之,它被字節(jié)碼所調(diào)用,然后將字節(jié)碼提供給解釋器。解釋器使用字節(jié)代碼,將代碼轉(zhuǎn)換為機(jī)器代碼,以適合在硬件上運(yùn)行。其實(shí),這與Java的工作原理非常相似,只不過(guò)字節(jié)代碼的生成是由程序員完成的,而字節(jié)代碼則可以被普遍共享。
雖然基線編譯器的使命是盡可能快地執(zhí)行代碼編譯,但是它還是會(huì)生成未經(jīng)優(yōu)化的字節(jié)碼。在解釋器中,這些字節(jié)碼會(huì)導(dǎo)致應(yīng)用程序的速度變慢。為此,Mozilla Firefox瀏覽器將Spider Monkey JavaScript進(jìn)行了嵌入,以?xún)?yōu)化并提高機(jī)器代碼的效率。例如:JavaScript引擎會(huì)直觀地了解到變量的數(shù)據(jù)類(lèi)型,進(jìn)而更好地生成少量的機(jī)器代碼。
此外,JavaScript引擎還可以根據(jù)代碼的執(zhí)行情況,通過(guò)收集和分析數(shù)據(jù),發(fā)現(xiàn)代碼運(yùn)行緩慢的原因,進(jìn)而給出優(yōu)化并替換的建議。
知名的JavaScript引擎
除了前面介紹過(guò)Firefox正在使用的Spider Monkey引擎,還有Internet Explorer正在使用的Chakra引擎,而Google使用的是V8引擎。雖然它們用到了不同的編譯器,但是它們所遵循的優(yōu)化結(jié)構(gòu)是相似的。
Google的V8:
由Lark Bak創(chuàng)建的The Chromium項(xiàng)目開(kāi)發(fā)了開(kāi)源的Javascript引擎。該項(xiàng)目為Google Chrome和Chromium網(wǎng)絡(luò)瀏覽器提供了開(kāi)發(fā)服務(wù)。Chrome的V8引擎幾乎是在Chrome的首個(gè)版本誕生(2008年9月2日)的同時(shí),就應(yīng)運(yùn)而生了。V8能夠很好地兼容Node.js和MongoDB等服務(wù)器端技術(shù)。
由于V8帶有Ignition解釋器,因此它能夠通過(guò)低級(jí)字節(jié)碼完成解釋與執(zhí)行。盡管這些低級(jí)字節(jié)碼比起機(jī)器碼來(lái)說(shuō)既小又慢,但它們需要編譯的時(shí)間會(huì)更少。我們可以使用Full-Codegen編譯器,來(lái)生成未優(yōu)化的代碼。該編譯器的運(yùn)行速度比其他編譯器要快一些。JIT編譯器—Turbofan不但可以編譯代碼,并且能夠密切注意代碼是否會(huì)在整個(gè)JavaScript執(zhí)行的過(guò)程中被多次用到。也就是說(shuō),它的垃圾收集器會(huì)觀察各種對(duì)象中不再引用到的數(shù)據(jù)。而且這種數(shù)據(jù)的收集工作是由收集器來(lái)完成的。值得一提的是,在執(zhí)行垃圾回收的周期內(nèi),V8引擎將會(huì)自動(dòng)停止程序的運(yùn)行。
CHAKRA:
由Microsoft開(kāi)發(fā)的Chakra JavaScript引擎,被用在了Microsoft Edge Web瀏覽器中。它是Internet Explorer中Jscript引擎的一個(gè)分支。我們可以簡(jiǎn)單地理解為:Chakra提高了Internet Explorer中JavaScript的執(zhí)行質(zhì)量。由于是用新的JavaScript編譯器組成,因此Chakra可以幫助用戶(hù)將JavaScript代碼編譯成為高級(jí)機(jī)器代碼。通過(guò)提供全新的解釋器,Chakra不但能夠在傳統(tǒng)的網(wǎng)頁(yè)上執(zhí)行腳本,而且能夠改進(jìn)JavaScript的運(yùn)行時(shí)和各種庫(kù)。
SPIDER MONKEY:
由Netscape Communications的Brendan Eich使用C和C++語(yǔ)言編寫(xiě)的Spider Monkey,最終被發(fā)布成為了開(kāi)源的JavaScript引擎。目前,它被用在包括Firefox在內(nèi)的許多享有Mozilla公共許可證(2.0版)的產(chǎn)品中。它將類(lèi)型推斷(type inference)與JIT編譯器—Jaegermonkey相連接,以生成有效的代碼。
在結(jié)構(gòu)上,Spider Monkey是由一個(gè)解釋器、幾個(gè)JIT編譯器、一個(gè)反編譯器、以及一個(gè)垃圾收集器所組成。
RHINO:
由Mozilla Foundation管理的Rhino JavaScript引擎,是一款完全由Java編寫(xiě)的開(kāi)源軟件。它同樣可以被使用在Mozilla Firefox中。
JavaScript運(yùn)行時(shí)
與其他編程語(yǔ)言不同的是,JavaScript是一種單線程的語(yǔ)言運(yùn)行時(shí)(language runtime),且只能一次性執(zhí)行并完成程序代碼。由于代碼是按照順序被執(zhí)行的,因此那些需要花費(fèi)較長(zhǎng)時(shí)間的代碼,可能會(huì)阻塞后續(xù)有待執(zhí)行的其他代碼。例如:當(dāng)您在瀏覽器(如:Google Chrome)上打開(kāi)某個(gè)網(wǎng)站時(shí),它會(huì)調(diào)用JavaScript的一個(gè)執(zhí)行線程,用來(lái)處理諸如滾動(dòng)網(wǎng)頁(yè),在網(wǎng)頁(yè)上打印部分內(nèi)容,偵聽(tīng)DOM事件,以及執(zhí)行某項(xiàng)操作等。然而,如果JavaScript一旦停止執(zhí)行,瀏覽器就會(huì)自動(dòng)停止所有各項(xiàng)操作。這就意味著瀏覽器在完成某項(xiàng)任務(wù)之前,并不會(huì)響應(yīng)其他的任何內(nèi)容。
根據(jù)概念,JavaScript運(yùn)行時(shí)是指JavaScript代碼所處的環(huán)境或條件。因此,當(dāng)JavaScript在Google Chrome上執(zhí)行時(shí),JavaScript運(yùn)行時(shí)便是v8;如果在Mozilla上,它就是Spider Monkey;如果在IE上,則為Chakra。
JavaScript運(yùn)行時(shí)的API提供了一種執(zhí)行桌面與服務(wù)器端應(yīng)用的方法。由于這些API在Windows 10和任何版本的Windows操作系統(tǒng)上可用,因此Windows操作系統(tǒng)可以通過(guò)使用Chakra的相關(guān)標(biāo)準(zhǔn),向應(yīng)用程序添加不同的腳本功能。當(dāng)然,目標(biāo)系統(tǒng)上也需要安裝好Internet Explorer 11。
內(nèi)聯(lián)緩存(Inline Caching)
內(nèi)聯(lián)緩存的概念源于經(jīng)驗(yàn)觀察。也就是說(shuō),通過(guò)觀察并記住以前直接在調(diào)用站點(diǎn)處那些相同方法的查詢(xún)結(jié)果,來(lái)加快運(yùn)行時(shí)方法的綁定速度,并提高查找的性能。同時(shí),為了簡(jiǎn)化此過(guò)程,我們可以為調(diào)用站點(diǎn)分配不同的狀態(tài)。例如:將最初的站點(diǎn)分配為“未初始化(uninitialized)”狀態(tài)。那么,語(yǔ)言運(yùn)行時(shí)到達(dá)未初始化的特定調(diào)用站點(diǎn)時(shí),將執(zhí)行動(dòng)態(tài)查找,并將結(jié)果存儲(chǔ)到調(diào)用站點(diǎn)中,同時(shí)將其狀態(tài)更改為“單態(tài)(monomorphic)”。后續(xù),如果語(yǔ)言運(yùn)行時(shí)再次到達(dá)同一調(diào)用站點(diǎn)時(shí),它會(huì)從現(xiàn)有的存儲(chǔ)中進(jìn)行檢索,并直接予以調(diào)用,而無(wú)需再次執(zhí)行任何其他的查找。此外,為了解決不同類(lèi)型的對(duì)象可能出現(xiàn)在同一調(diào)用站點(diǎn)的可能性,語(yǔ)言運(yùn)行時(shí)還必須在代碼中插入保護(hù)條件(guard conditions)。
至此,希望您已經(jīng)對(duì)JavaScript引擎的相關(guān)概念和工作原理有所了解。希望您能更好地將其運(yùn)用到項(xiàng)目編程中。
原文標(biāo)題:How JavaScript Engine Works?,作者:Vyom Srivastava
【51CTO譯稿,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文譯者和出處為51CTO.com】