新聞動(dòng)態(tài)
JavaScript 運(yùn)行原理
常見問(wèn)題 發(fā)布者:cya 2019-12-12 08:35 訪問(wèn)量:266
譯文:梁天培
鏈接:https://juejin.im/post/5d84ae08e51d4561a705bbde
原文鏈接:https://blog.bitsrc.io/javascript-under-the-hood-632ccae06b27
最初,JavaScript 只能在 Web 瀏覽器中運(yùn)行,但是隨著 Node 的出現(xiàn),現(xiàn)在 JavaScript 也可以在服務(wù)端運(yùn)行。雖然我們可能知道應(yīng)該在何時(shí)何地去使用它, 但是我們真的了解這些腳本執(zhí)行的背后發(fā)生了什么嗎?
如果您覺(jué)得自己對(duì) JavaScript 引擎有了一些了解的話,可以先給自己鼓個(gè)掌,但不要急著關(guān)掉本文,我相信閱讀完成后您仍然可以從中學(xué)到一些東西。
JavaScript 是一門高級(jí)語(yǔ)言,但是最終計(jì)算機(jī)能理解只有1和0。那么我們編寫的代碼是如何被計(jì)算機(jī)理解的呢?掌握所學(xué)編程語(yǔ)言的基礎(chǔ)知識(shí)將讓您能編寫出更好的代碼。在本文中,我們僅探討一個(gè)問(wèn)題:JavaScript 是如何工作的?
這是本文將要探索的主要內(nèi)容,它負(fù)責(zé)使計(jì)算機(jī)理解我們編寫的 JS 代碼。JavaScript 引擎是一種用于將我們的代碼轉(zhuǎn)換為機(jī)器可讀語(yǔ)言的引擎。如果沒(méi)有 JavaScript 引擎,您編寫的代碼對(duì)計(jì)算機(jī)來(lái)說(shuō)簡(jiǎn)直是一堆“胡言亂語(yǔ)”。不僅僅是 JavaScript ,其他所有編程語(yǔ)言都需要一個(gè)類似的引擎,來(lái)將這些“胡言亂語(yǔ)”轉(zhuǎn)換成對(duì)計(jì)算機(jī)有意義的語(yǔ)言。
目前有多種 JavaScript 引擎在可供使用。您可以在 Wikipedia 上查閱所有可用的 JavaScript 引擎。它們也被稱為 ECMAScript 引擎,這樣叫的具體原因會(huì)在下文中提及。下面是一些我們?nèi)粘?赡軙?huì)用到的 JavaScript 引擎:
Chakra, Microsoft IE/Edge
SpiderMonkey, FireFox
V8, Chrome
除此之外的其它引擎,可以自行搜索了解。接下來(lái),我們將深入研究這些引擎,以了解它們是如何翻譯 JavaScript 文件的。
我們已經(jīng)知道了引擎是必須的,由此可能不禁會(huì)想:
是誰(shuí)發(fā)明了 JavaScript 引擎?
答案是,任何人都可以。它只是分析我們的代碼并將其翻譯的另一種語(yǔ)言的工具。V8 是最受歡迎的 JavaScript 引擎之一,也是 Chrome 和 NodeJS 使用的引擎。它是用 C++(一種底層語(yǔ)言)編寫的。但是如果每個(gè)人都創(chuàng)造一個(gè)引擎,那場(chǎng)面就不是可控范圍內(nèi)的了。
因此,為了給這些引擎確立一個(gè)規(guī)范,ECMA 的標(biāo)準(zhǔn)誕生了,該標(biāo)準(zhǔn)主要提供如何編寫引擎和 JavaScript 所有功能的規(guī)范。這就是新功能能在 ECMAScript 6、7、8 上實(shí)現(xiàn)的原因。同時(shí),引擎也進(jìn)行了更新以支持這些新功能。于是,我們便可以在開發(fā)過(guò)程中檢查了瀏覽器中 JS 高級(jí)功能的可用性。
下面我們對(duì) V8 引擎進(jìn)行進(jìn)一步的探索,因?yàn)榛靖拍钤谒幸嬷惺且恢碌摹?/span>
JavaScript V8 Engine
上圖就是 JS Engine 內(nèi)部的工作流程。我們輸入的代碼將通過(guò)以下階段,
Parser
AST
Interpreter 生成 ByteCode
Profiler
Compiler 生成優(yōu)化后的代碼
別被上面的流程給唬住了,在幾分鐘后您將了解它們是協(xié)同運(yùn)作的。
在進(jìn)一步深入這些階段之前,您需要先了解 Interpreter 和 Compiler 的區(qū)別。
通常,將代碼轉(zhuǎn)換成機(jī)器可讀語(yǔ)言的方法有兩種。我們將要討論的概念不僅適用于 JavaScript ,而且適用于大多數(shù)編程語(yǔ)言,例如 Python,Java 等。
Interpreter 逐行讀取代碼并立即執(zhí)行。
Compiler 讀取您的整個(gè)代碼,進(jìn)行一些優(yōu)化,然后生成優(yōu)化后的代碼。
讓我們來(lái)看下面這個(gè)例子。
function add(a, b) {
return a+b
}
for(let i = 0; i < 1000; i++) {
add(1 + 1)
}
上面的示例循環(huán)調(diào)用了 add 函數(shù)1000次,該函數(shù)將兩個(gè)數(shù)字相加并返回總和。
Interpreter 接收上面的代碼后,它將逐行讀取并立即執(zhí)行代碼,直到循環(huán)結(jié)束。它的工作僅僅是實(shí)時(shí)地將代碼轉(zhuǎn)換為我們的計(jì)算機(jī)可以理解的內(nèi)容。
如果這段代碼接受者是 Compiler,它會(huì)先完整地讀取整個(gè)程序,對(duì)我們要執(zhí)行的代碼進(jìn)行分析,并生成電腦可以讀懂的機(jī)器語(yǔ)言。過(guò)程如同獲取 X(我們的JS文件)并生成 Y(機(jī)器語(yǔ)言)一樣。如果我們使用 Interpreter 執(zhí)行 Y,則會(huì)獲得與執(zhí)行 X 相同的結(jié)果。
從上圖中可以看出,ByteCode 只是中間碼,計(jì)算機(jī)仍需要對(duì)其進(jìn)行翻譯才能執(zhí)行。但是 Interpreter 和 Compiler 都將源代碼轉(zhuǎn)換為機(jī)器語(yǔ)言,它們唯一的區(qū)別在于轉(zhuǎn)換的過(guò)程不盡相同。
Interpreter 逐行將源代碼轉(zhuǎn)換為等效的機(jī)器代碼。
Compiler 在一開始就將所有源代碼轉(zhuǎn)換為機(jī)器代碼。
如果你想了解更多它們之前的區(qū)別,推薦閱讀這篇文章。
當(dāng)您閱讀完上面的推薦文章后,您可能已經(jīng)了解到 Babel 實(shí)際上是一個(gè) JS Compiler ,它可以接收您編寫的新版本 JS 代碼并向下編譯為與瀏覽器兼容的 JS 代碼(舊版本的 JS 代碼)。
Interpreter 的優(yōu)點(diǎn)是無(wú)需等待編譯即可立即執(zhí)行代碼。這對(duì)在瀏覽器中運(yùn)行 JS 提供了極大的便利,因?yàn)樗杏脩舳疾幌肜速M(fèi)時(shí)間在等待代碼編譯這件事上。但是,當(dāng)有大量的 JS 代碼需要執(zhí)行時(shí)會(huì)運(yùn)行地比較慢。還記得上面例子中的那一小段代碼嗎?代碼中執(zhí)行了1000次函數(shù)調(diào)用。函數(shù) add 被調(diào)用了1000次,但他的輸出保持不變。但是 Interpreter 還是逐行執(zhí)行,會(huì)顯得比較慢。
在同樣的情況下,Compiler 可以通過(guò)用2代替循環(huán)(因?yàn)?add 函數(shù)每次都是執(zhí)行1 + 1)來(lái)進(jìn)行一些優(yōu)化。Compiler 最終給出的優(yōu)化代碼可以在更短的時(shí)間內(nèi)執(zhí)行完成。
綜上所述,Interpreter 可以立即開始執(zhí)行代碼,但不會(huì)進(jìn)行優(yōu)化。Compiler 雖然需要花費(fèi)一些時(shí)間來(lái)編譯代碼,但是會(huì)生成對(duì)執(zhí)行時(shí)更優(yōu)的代碼。
好的,Interpreter 和 Compiler 必要知識(shí)我們已經(jīng)了解了。現(xiàn)在讓我們回到主題——JS 引擎。
因此,考慮到編譯器和解釋器的優(yōu)缺點(diǎn),如果我們同時(shí)利用兩者的優(yōu)點(diǎn),該怎么辦?這就是 JIT(Just In Time) Compiler 的用武之地。它是 Interpreter 和 Compiler 的結(jié)合,現(xiàn)在大多數(shù)瀏覽器都在更快,更高效地實(shí)現(xiàn)此功能。同時(shí) V8 引擎也使用此功能。
JavaScript V8 Engine
在這個(gè)過(guò)程中,
Parser 是一種通過(guò)各種 JavaScript 關(guān)鍵字來(lái)識(shí)別,分析和分類程序各個(gè)部分的解析器。它可以區(qū)分代碼是一個(gè)方法還是一個(gè)變量。
然后,AST(抽象語(yǔ)法樹) 基于 Parser 的分類構(gòu)造樹狀結(jié)構(gòu)。您可以使用 AST Explorer 查看該樹的結(jié)構(gòu)。
隨后將 AST 提供給 Interpreter 生成 ByteCode。如上文所述,ByteCode 不是最底層的代碼,但可以被執(zhí)行。在此階段,瀏覽器借助 V8 引擎執(zhí)行 ByteCode 進(jìn)行工作,因此用戶無(wú)需等待。
同時(shí),Profiler 將查找可以被優(yōu)化的代碼,然后將它們傳遞給 Compiler。Compiler 生成優(yōu)化代碼的同時(shí),瀏覽器暫時(shí)用 ByteCode 執(zhí)行操作。并且,一旦 Compiler 生成了優(yōu)化代碼,優(yōu)化代碼則將完全替換掉臨時(shí)的 ByteCode。
通過(guò)這種方式,我們可以充分利用 Interpreter 和 Compiler 的優(yōu)點(diǎn)。Interpreter 執(zhí)行代碼的同時(shí),Profiler 尋找可以被優(yōu)化的代碼,Compiler 則創(chuàng)建優(yōu)化的代碼。然后,將 ByteCode 碼替換為優(yōu)化后的較為底層的代碼,例如機(jī)器代碼。
這僅意味著性能將在逐漸提高,同時(shí)不會(huì)有阻塞執(zhí)行的時(shí)間。
作為機(jī)器代碼,ByteCode 不能被所有計(jì)算機(jī)理解及執(zhí)行。它仍然需要像虛擬機(jī)或像 Javascript V8 引擎這樣的中間件才能將其轉(zhuǎn)換為機(jī)器可讀的語(yǔ)言。這就是為什么我們的瀏覽器可以在上述5個(gè)階段中借助 JavaScript 引擎在 Interpreter 中執(zhí)行 ByteCode 的原因。
所以您可以會(huì)有另一個(gè)問(wèn)題,
JavaScript 是但不完全是一門解釋型語(yǔ)言。Brendan Eich 最初是在 JavaScript 的早期階段創(chuàng)建 JavaScript 引擎 “ SpiderMonkey” 的。該引擎有一個(gè) Interpreter 來(lái)告訴瀏覽器該怎么執(zhí)行代碼。但是現(xiàn)在我們的引擎不僅包括了 Interpreter,還有 Compiler。我們的代碼不僅可以被轉(zhuǎn)換成 ByteCode,還可以被編譯輸出優(yōu)化后的代碼。因此,從技術(shù)上講,這完全取決于引擎是如何實(shí)現(xiàn)的。
JavaScript 引擎的整體工作原理就是這樣。相信您無(wú)需學(xué)習(xí) JavaScript 也可以理解。當(dāng)然,您甚至可以在不知道 JavaScript 如何工作的情況下編寫代碼。但是,如果我們了解一些幕后的知識(shí),或許能讓我們編寫出更好的代碼。
關(guān)鍵字: JavaScript 運(yùn)行原理 開封網(wǎng)站建設(shè)
文章連接: http://m.hsjyfc.com.cn/cjwt/643.html
版權(quán)聲明:文章由 晨展科技 整理收集,來(lái)源于互聯(lián)網(wǎng)或者用戶投稿,如有侵權(quán),請(qǐng)聯(lián)系我們,我們會(huì)立即刪除。如轉(zhuǎn)載請(qǐng)保留
晨展解決方案
晨展新聞