一篇給小白看的 JavaScript 引擎指南
有時編寫 Web 應用的代碼會感覺充滿魔力,因為我們只是寫了一系列字元,就能在瀏覽器裡看到效果了。但是理解魔法背後的技術,可以協助你更好地提高編程技巧。至少當你試 圖解釋在 JavaScript 驅動的 web 或行動裝置 App的幕後發生了什麼的時候,會覺得自己不那麼白癡了。
很多年前,那是我還是個研究生講師,向一個教授抱怨還沒有掌握那些特別難懂的法語文法點,可以教給我的本科學生。我記得當時她說的話:“有時候,學習某個事物的唯一方式就是教授它。”
嘗試向工程師解釋 NativeScript 是如何通過 JavaScript 引擎在幕後工作、 在運行時串連調用原生的 APIs——面對這樣一件複雜的工作很容易在一片雜草中迷失方向。事實上,任何 JavaScript 開發人員都應該對我們每天使用的這門技術基礎的引擎感到好奇。現在我們一起來仔細分析下 JavaScript 引擎到底做了什麼,為什麼不同的平台使用不同引擎,多年來它們是如何發展的,以及作為開發人員我們為什麼要關注這些。
首先,一些專業術語
“JavaScript 引擎”通常被稱作一種 虛擬機器。“虛擬機器”是指軟體驅動的給定的電腦系統的模擬器。有很多類型的虛擬機器,它們根據自己在多大程度上精確地類比或代替真實的物理機器來分類。
例如,“系統虛擬機器”提供了一個可以運行作業系統的完整模擬平台。Mac 使用者很熟悉的 Parallels 就是一個允許你在 Mac 上運行 Windows系統虛擬機器。
另一方面,“進程虛擬機器”不具備全部的功能,能運行一個程式或者進程。Wine 是一個允許你在 Linux 機器上運行 Windows 應用的進程虛擬機器,但是並不在 Linux 中提供完整的 Windows 作業系統。
JavaScript 虛擬機器是一種進程虛擬機器,專門設計來解釋和執行的 JavaScript 代碼。
注意:要區別在瀏覽器中排布頁面配置的 布局引擎 和解釋和執行代碼的底層 JavaScript 引擎是非常重要的。在 這裡 可以找到一個很好的闡釋。
那麼,確切來講,到底什麼是 JavaScript 引擎,它做了什嗎?
JavaScript 引擎的基本工作是把開發人員寫的 JavaScript 代碼轉換成高效、最佳化的代碼,這樣就可以通過瀏覽器進行解釋甚至嵌入到應用中。事實上,JavaScriptCore 自稱為“最佳化虛擬機器”。
更準確地講,每個 JavaScript 引擎都實現了一個版本的 ECMAScript,JavaScript 是它的一個分支。隨著 ECMAScript 的不斷髮展,JavaScript 引擎也不斷改進。之所以有這麼多不同的引擎,是因為它們每個都被設計運行在不同的 web 瀏覽器、headless 瀏覽器、或者像 Node.js 那樣的運行時環境中。
你也許熟悉 網頁瀏覽器,那什麼是 headless 瀏覽器呢?它是一個沒有圖形化使用者介面的 網頁瀏覽器。它們在對 web 產品進行自動化測試時十分有用。一個很棒的例子就是 PhantomJS。那 Node.js 又和 JavaScript 引擎有什麼關係?Node.js 是一個非同步、事件驅動的架構,讓你在伺服器端可以使用 JavaScript。既然他們是驅動 JavaScript 的工具,所以它們也是由 JavaScript 引擎驅動。
按照上述關於虛擬機器的定義,把 JavaScript 引擎稱作進程虛擬機器就很好理解了,因為它的唯一的目的就是讀取和編譯 JavaScript 代碼。這並不意味著它只是個簡單的引擎。比如,JavaScriptCore 就有六個“構建模組”可以分析、解釋、最佳化、記憶體回收 JavaScript 代碼。
它是如何工作的?
當然,這決定於引擎。吸引我們注意的兩個主要的引擎都利用了 NativeScript ,它們分別是 WebKit 的 JavaScriptCore 和 Google 的 V8 引擎。這兩個引擎使用不同的方式處理代碼。
JavaScriptCore 執行 一系列步驟 來解釋和最佳化指令碼:
什嗎?簡單來說,JavaScript 引擎會載入你的原始碼,把它分解成字串又叫做分詞),再 把這些字串轉換 成編譯器可以理解的位元組碼,然後執行這些位元組碼。
Google 的 V8 引擎 是用 C++ 編寫的,它也能夠編譯並執行 JavaScript 原始碼、處理記憶體配置和記憶體回收。它被設計成由兩個編譯器組成,可以把源碼直接編譯成機器碼:
如果 Crankshaft 確定需要最佳化的代碼是由 Full-codegen 產生的未最佳化代碼,它就會取代 Full-codegen,這個過程叫做“crankshafting”。
一旦編譯過程中產生了機器代碼,引擎就會向瀏覽器暴露所有的資料類型、操作符、對象、在 ECMA 標準中指定的函數、或任何運行時需要使用的東西,NativeScript 就是如此。
有哪些 JavaScript 引擎?
有一大堆令人眼花繚亂的 JavaScript 引擎可以用來解釋、分析和執行你的用戶端代碼。每個瀏覽器版本發布時,它的 JavaScript 引擎都可能有所改變或最佳化以跟上 JavaScript 代碼執行技術的狀況的變化。
你還沒被這些瀏覽器引擎的名字完全弄糊塗之前,請記住很多市場營銷的元素被加入了這些引擎和以它們為基礎的瀏覽器。這篇對 JavaScript 編譯 十分有用的分析 中,作者諷刺地指出:“你所不知道的是,編譯器大約有 37% 是由市場營銷構成的,對編譯器進行品牌重塑也是你能做的為數不多的事情之一,智慧的市場營銷,故而有了一系列名字:SquirrelFish、Nitro、SFX……”。
在牢記營銷對命名和重新命名這些引擎的影響的同時,注意到幾件在 JavaScript 引擎發展史上的重大事件是很有用的。我為你做了一個便於理解的圖表:
| Browser, Headless Browser, or Runtime |
JavaScript Engine |
| Mozilla |
Spidermonkey |
| Chrome |
V8 |
| Safari |
JavaScriptCore |
| IE and Edge |
Chakra |
| PhantomJS |
JavaScriptCore |
| HTMLUnit |
Rhino |
| TrifleJS |
V8 |
| Node.js |
V8 |
| Io.js* |
V8 |
*JavaScriptCore 被改寫為 SquirrelFish,升級版本為 QuirrelFish Extreme,也叫做 Nitro。然而,構成 Webkit 實現基礎的 JavaScript 引擎就是 JavaScriptCore比如 Safari)。
**iOS 開發人員應該要知道行動裝置的 Safari 使用 Nitro,但是 UIWebView 不包括 JIT 編譯,所以體驗會慢一些。然而開發人員可以在 iOS8 中使用包含 Nitro 的 WKWebView,使用體驗 明顯 變快。混合行動裝置 App程式的開發人員應該能鬆口氣了。
*最終 io.js 從 Node.js 分離開的原因之一就是為了支援 V8 版本的引擎。這仍然是一個挑戰,正如 這裡 講述的。
我們為什麼要關注?
JavaScript 引擎的代碼解析和執行過程的目標就是在最短時間內編譯出最佳化的代碼。
最重要的是,這些引擎的演化與我們對發展 web 和 移動平台的不斷探究息息相關,讓它們儘可能具有高效能,是相輔相成的。為了追蹤這種演化,你可以看到各種各樣的引擎在基準圖中是如何表現的,就好像 arewefastyet.com 總結的。例如,比較 Chrome 在搭載 V8 引擎與 non-Crankshafted 引擎時的表現就很有趣。
任何一個 web 開發人員都要意識到,我們努力編寫、調試和維護的代碼在不同瀏覽器中執行效果必然有所差異。為什麼某段代碼在一個瀏覽器上工作得很慢,但在另一個上卻快得多?
同樣地,移動開發人員,尤其是使用 webview 顯示頁面內容的混合行動裝置 App開發人員,或者那些使用像 NativeScript 這種運行時環境的開發人員,想知道是什麼引擎在解釋執行他們的 JavaScript 代碼。移動 web 開發人員應該注意到那些小小裝置上的瀏覽器所具備的各種局限性和可能性。作為一個想持續發展的 web、移動或應用程式開發人員,時刻關注 JavaScript 引擎的變化會帶給你超值回報。