什么是JavaScript引擎

什么是JavaScript引擎?

簡(jiǎn)單來(lái)講,就是能夠提供執(zhí)行JavaScript代碼的運(yùn)行環(huán)境。要解釋這一概念,需要了解一些編譯原理的基礎(chǔ)概念和現(xiàn)代語(yǔ)言需要的一些新編譯技術(shù)。

  1. 首先來(lái)看C/C++語(yǔ)言 - 編譯執(zhí)行。
    它們是比較悠久的語(yǔ)言了,實(shí)際上就是使用編譯器直接將它們編譯成本地代碼,這一切都是由開(kāi)發(fā)人員在代碼編寫(xiě)完成之后實(shí)施。用戶只是使用這些編譯好的本地代碼,這些本地代碼被系統(tǒng)的加載器加載執(zhí)行,這些本地代碼(也就是機(jī)器指令)由操作系統(tǒng)調(diào)度CPU直接執(zhí)行,無(wú)需其它額外的輔助虛擬機(jī)等。這一過(guò)程基本上是從源代碼開(kāi)始,然后抽象語(yǔ)法樹(shù),之后中間表示,最后到本地代碼。

  2. 其次,來(lái)看看Python等腳本語(yǔ)言 - 解釋執(zhí)行。
    處理腳本語(yǔ)言通常的做法是開(kāi)發(fā)者將寫(xiě)好的代碼直接交給用戶,用戶使用腳本的解釋器將腳本文件加載然后解釋執(zhí)行。當(dāng)然,現(xiàn)在Python也可以支持將腳本編譯生成中間表示,當(dāng)然這是后話。所以這表示,對(duì)于腳本語(yǔ)言,沒(méi)有開(kāi)發(fā)人員的編譯過(guò)程,當(dāng)然也不是絕對(duì),這主要因?yàn)槭褂脠?chǎng)景不一樣,它的目的不是高性能。這一過(guò)程是源代碼,到抽象語(yǔ)法樹(shù),再到解釋器解釋執(zhí)行。

  3. 然后來(lái)看看Java語(yǔ)言。
    其做法可以理解為比較明顯的兩個(gè)階段:

    • 首先是像C++語(yǔ)言一樣的編譯器,但是,同C++編譯器生成的本地代碼結(jié)果不同,經(jīng)過(guò)編譯器編譯之后的是字節(jié)碼,字節(jié)碼是平臺(tái)無(wú)關(guān)的。
    • 在運(yùn)行字節(jié)碼階段,Java的運(yùn)行環(huán)境也就是Java虛擬機(jī)會(huì)加載字節(jié)碼,使用解釋執(zhí)行這些字節(jié)碼。

如果僅是這樣,那Java的性能就差C++太多了?,F(xiàn)代Java虛擬機(jī)一般都引入了JIT技術(shù),也就是前面說(shuō)的將字節(jié)碼轉(zhuǎn)變成本地代碼來(lái)提高執(zhí)行效率。這主要兩個(gè)階段,第一階段對(duì)時(shí)間要求不嚴(yán)格,第二階段則對(duì)每個(gè)步驟所花費(fèi)的時(shí)間非常敏感,時(shí)間越短越好。


最后回到JavaScript語(yǔ)言上來(lái)。前面已經(jīng)說(shuō)了它是一種解釋性腳本語(yǔ)言。是的,它的確是,但是隨著眾多工程師不斷投入資源來(lái)提高它的速度,這使得它能夠使用了Java虛擬機(jī)和C++編譯器中眾多的技術(shù),它的工作方式也在演變:

  • 早期也是一樣由解釋器來(lái)解釋它們即可,就是將源代碼轉(zhuǎn)變成抽象語(yǔ)法樹(shù),然后在抽象語(yǔ)法樹(shù)上解釋執(zhí)行。
  • 隨著將Java虛擬機(jī)的JIT技術(shù)引入,現(xiàn)在的做法是將抽象語(yǔ)法樹(shù)轉(zhuǎn)成中間表示(也就是字節(jié)碼),然后通過(guò)JIT技術(shù)轉(zhuǎn)成本地代碼,這能夠大大的提高了執(zhí)行效率。當(dāng)然也有些做法直接從抽象語(yǔ)法樹(shù)生成本地代碼的JIT技術(shù),例如V8。

這是因?yàn)镴avaScript跟Java還是有以下一些區(qū)別的:

  • 其一當(dāng)然是類型,JavaScript是無(wú)類型的語(yǔ)言,這使得對(duì)于對(duì)象的表示和屬性的訪問(wèn)上比Java存在比較大的性能損失。不過(guò)現(xiàn)在有一些新的技術(shù),參考C++或者Java的類型系統(tǒng)的優(yōu)點(diǎn),構(gòu)建隱式的類型信息,這些后面逐一介紹。
  • 其二是Java語(yǔ)言通常是將源代碼編譯成字節(jié)碼,這個(gè)同執(zhí)行階段是分開(kāi)的,也就是從源代碼到抽象語(yǔ)法樹(shù)到字節(jié)碼這段時(shí)間的長(zhǎng)短是無(wú)所謂的(或者說(shuō)不是特別重要),所以主要是盡可能的生成高效的字節(jié)碼即可。而對(duì)于JavaScript而言,這些都是在網(wǎng)頁(yè)和JavaScript文件下載后同執(zhí)行階段一起在網(wǎng)頁(yè)的加載和渲染過(guò)程中來(lái)實(shí)施的,所以對(duì)它們的處理時(shí)間也有著很高的要求。

描述JavaScript代碼執(zhí)行的過(guò)程,這一過(guò)程中因?yàn)椴煌夹g(shù)的引入,導(dǎo)致非常復(fù)雜,而且因?yàn)槎际窃诖a運(yùn)行過(guò)程中來(lái)處理這些步驟,所以每個(gè)階段時(shí)間越少越好,而且每引入一個(gè)階段都是額外的時(shí)間開(kāi)銷,可能最后的本地代碼執(zhí)行效率很高,但是之前步驟如果耗費(fèi)太多時(shí)間的話,最后的執(zhí)行結(jié)果可能并不會(huì)好。所以不同的JavaScript引擎選擇了不同的路徑,這里先不仔細(xì)介紹,后面再描述它們。

所以一個(gè)JavaScript引擎不外乎包括以下部分:
第一, 編譯器。主要工作是將源代碼編譯成抽象語(yǔ)法樹(shù),然后在某些引擎中還包含將抽象語(yǔ)法樹(shù)轉(zhuǎn)換成字節(jié)碼。
第二, 解釋器。在某些引擎中,解釋器主要是接受字節(jié)碼,解釋執(zhí)行這個(gè)字節(jié)碼,然后也依賴來(lái)及回收機(jī)制等。
第三, JIT工具。一個(gè)能夠能夠JIT的工具,將字節(jié)碼或者抽象語(yǔ)法樹(shù)轉(zhuǎn)換成本地代碼,當(dāng)然它也需要依賴?yán)斡?br> 第四, 垃圾回收器和分析工具(profiler)。它們負(fù)責(zé)垃圾回收和收集引擎中的信息,幫助改善引擎的性能和功效。

JavaScript引擎和渲染引擎

網(wǎng)頁(yè)的工作過(guò)程需要使用到兩個(gè)引擎,也就是渲染引擎和JavaScript引擎。從模塊上看,目前,它們是兩個(gè)獨(dú)立的模塊,分別負(fù)責(zé)不同的事情:JavaScript引擎負(fù)責(zé)執(zhí)行JavaScript代碼,而渲染引擎負(fù)責(zé)渲染網(wǎng)頁(yè)。

JavaScript引擎提供調(diào)用接口被渲染引擎使用,渲染引擎使用JavaScript引擎來(lái)處理JavaScript代碼并獲取結(jié)果。這當(dāng)然不是全部,事情不是這么簡(jiǎn)單,JavaScript引擎需要能夠訪問(wèn)渲染引擎構(gòu)建的DOM樹(shù),所以JavaScript引擎通常需要提供橋接的接口,而渲染引擎則根據(jù)橋接接口來(lái)提供讓JavaScript訪問(wèn)DOM的能力。

在現(xiàn)在眾多的HTML5能力中,很多都是通過(guò)JavaScript接口提供給開(kāi)發(fā)者的,所以這部分同樣需要根據(jù)橋接接口來(lái)實(shí)現(xiàn)具體類,以便讓JavaScript引擎能夠回調(diào)渲染引擎的具體實(shí)現(xiàn)。下圖是一個(gè)簡(jiǎn)單的關(guān)于兩者引擎的關(guān)系。

Paste_Image.png

在WebKit/Blink項(xiàng)目中,這兩種引擎通過(guò)橋接接口來(lái)訪問(wèn)DOM結(jié)構(gòu),這對(duì)性能來(lái)說(shuō)是一個(gè)重大的損失,因?yàn)槊看蜫avaScript代碼訪問(wèn)DOM都需要通過(guò)復(fù)雜和低效的橋接接口來(lái)完成。鑒于訪問(wèn)DOM樹(shù)的普遍性,這是一個(gè)常見(jiàn)的問(wèn)題。希望以后可以減少這一方面的開(kāi)銷。

JavaScriptCore引擎

JavaScriptCore引擎是WebKit中默認(rèn)的引擎,在早期階段,它的性能不是特別突出。特別是,它只有解釋器來(lái)解釋執(zhí)行JavaScript代碼。這一效率十分的低效,當(dāng)然從2008年開(kāi)始,JavaScriptCore引擎開(kāi)始一個(gè)新的優(yōu)化工作,就是重新實(shí)現(xiàn)了編譯器和字節(jié)碼解釋器,這就是SquirrelFish。該工作對(duì)于引擎的性能優(yōu)化作了比較大的改進(jìn)。隨后,蘋(píng)果內(nèi)部代號(hào)為”Nitro”的JavaScript引擎也是基于JavaScriptCore項(xiàng)目的,它的性能還是非常出色的,鑒于其是內(nèi)部項(xiàng)目,所以具體還有什么特別的處理就不得而知了。在這之后,開(kāi)發(fā)者們又將內(nèi)嵌緩存、基于正則表達(dá)式的JIT和簡(jiǎn)單的JIT引入到JavaScriptCore中。然后,又陸續(xù)加入的字節(jié)碼解釋器??梢钥闯觯琂avaScriptCore引擎也在不斷的高速發(fā)展中。

V8引擎

V8是Google的一個(gè)開(kāi)源項(xiàng)目,現(xiàn)在成為了JavaScript引擎和眾多相關(guān)技術(shù)的引領(lǐng)者。它的目的很簡(jiǎn)單,就是為了提高性能。因?yàn)橹安还躂avaScriptCore引擎還是其它JavaScript引擎,在當(dāng)時(shí)的情況下,它們的性能都不能令人非常滿意。為了達(dá)到高性能的JavaScript代碼執(zhí)行效率從而獲得更好的網(wǎng)頁(yè)瀏覽效果,它甚至采用直接將JavaScript編譯成本地代碼的方式。

V8支持眾多的操作系統(tǒng),包括但是不限于Windows、Linux、Android、Mac OS X等。同時(shí)它也是能夠支持眾多的硬件架構(gòu),例如IA32、X64、ARM、MIPS等。這么看下來(lái),它將主流軟硬件平臺(tái)一網(wǎng)打盡,由于它是一個(gè)開(kāi)源項(xiàng)目,開(kāi)發(fā)者可以自由的使用它的強(qiáng)大能力,一個(gè)例子就是目前炙手可熱的NodeJS項(xiàng)目,它就是基于V8項(xiàng)目的。開(kāi)源的好處就是大家可以很方便地學(xué)習(xí)、貢獻(xiàn)和使用,下圖是V8引擎最基礎(chǔ)的代碼執(zhí)行過(guò)程。

V8

從圖中可以看出,首先它也是將源代碼轉(zhuǎn)變成抽象語(yǔ)法樹(shù)的,這一點(diǎn)同JavaScriptCore引擎一樣,之后兩個(gè)引擎開(kāi)始分道揚(yáng)鑣。不同于JavaScriptCore引擎,V8引擎并不將抽象語(yǔ)法樹(shù)轉(zhuǎn)變成字節(jié)碼或者其它中間表示,而是通過(guò)JIT編譯器的全代碼生成器(full code generator)從抽象語(yǔ)法樹(shù)直接生成本地代碼,所以沒(méi)有像Java一樣的虛擬機(jī)或者字節(jié)碼解釋器。

這樣做的原因,主要是因?yàn)闇p少這抽象語(yǔ)法樹(shù)到字節(jié)碼的轉(zhuǎn)換時(shí)間,這一切都在網(wǎng)頁(yè)加載時(shí)候完成,雖然可以提高優(yōu)化的可能,但是這些分析可能帶來(lái)巨大的時(shí)間浪費(fèi)。

當(dāng)然,缺點(diǎn)也很明顯,至少包括兩點(diǎn):

  • 第一是某些JavaScript使用場(chǎng)景其實(shí)使用解釋器更為合適,因?yàn)闆](méi)有必要生成本地代碼;
  • 第二是因?yàn)闆](méi)有中間表示,會(huì)減少優(yōu)化的機(jī)會(huì)因?yàn)槿鄙僖粋€(gè)中間表示層。

在之后的版本中,V8工程師們引入了Crankshaft編譯器,它能夠?qū)狳c(diǎn)的JS函數(shù)進(jìn)行分析和優(yōu)化后再生成本地代碼,原因在于不是所有的JavaScript代碼都合適做如此深層次的優(yōu)化,因?yàn)閮?yōu)化本身也需要花費(fèi)一定的時(shí)間。

目前V8和JavaScriptCore引擎各有特色,相信兩者的發(fā)展能夠不斷推進(jìn)JavaScript語(yǔ)言的執(zhí)行性能。

本文參考http://blog.csdn.net/milado_nju/article/details/22101681

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容