移動(dòng)端 JS 引擎哪家強(qiáng)?美國(guó)硅谷找......
本文轉(zhuǎn)載自微信公眾號(hào)「鹵蛋實(shí)驗(yàn)室」,作者鹵代烴。轉(zhuǎn)載本文請(qǐng)聯(lián)系鹵蛋實(shí)驗(yàn)室公眾號(hào)。
在一般的移動(dòng)端開發(fā)場(chǎng)景中,每次更新應(yīng)用功能都是通過 Native 語言開發(fā)并通過應(yīng)用市場(chǎng)版本分發(fā)來實(shí)現(xiàn)的。
但是市場(chǎng)瞬息萬變,Native 語言在開發(fā)效率上存在一定不足,并且從 APP 版本更新 到 應(yīng)用市場(chǎng)審核發(fā)布 再到 用戶下載更新,總會(huì)存在一定的時(shí)間差,這樣就導(dǎo)致新的功能無法及時(shí)覆蓋全量用戶。
為了解決這個(gè)問題,開發(fā)者們一般會(huì)在項(xiàng)目里引入一門腳本語言,提速 APP 的研發(fā)流程。
在移動(dòng)端應(yīng)用比較廣泛的腳本語言有 Lua 和 JavaScript,前者在游戲領(lǐng)域用的比較多,后者在應(yīng)用領(lǐng)域用的比較多。本篇文章主要是想探討一下移動(dòng)雙端(iOS & Android)的 JavaScript 引擎選型。由于個(gè)人水平有限,文章總會(huì)有遺漏和不足的地方,還請(qǐng)各位大佬多多指教。
JS 引擎選型要點(diǎn)
JavaScript 作為世界上最熱門的腳本語言,有著非常多的引擎實(shí)現(xiàn):有 Apple 御用的 JavaScriptCore,有性能最強(qiáng)勁的 V8,還有最近熱度很高的 QuickJS......如何從這些 JS 引擎里選出最適合的?我個(gè)人認(rèn)為要有幾個(gè)考量:
- 性能:這個(gè)沒話說,肯定是越快越好
- 體積:JS 引擎會(huì)增加一定的包體積
- 內(nèi)存占用:內(nèi)存占用越少越好
- JavaScript 語法支持程度:支持的新語法越多越好
- 調(diào)試的便捷性:是否直接支持 debug?還是需要自己編譯實(shí)現(xiàn)調(diào)試工具鏈
- 應(yīng)用市場(chǎng)平臺(tái)規(guī)范:主要是 iOS 平臺(tái),平臺(tái)禁止應(yīng)用集成帶 JIT 功能的虛擬機(jī)
比較麻煩的是,上面的幾個(gè)點(diǎn)都不是互相獨(dú)立的:
比如說開啟 JIT 的 V8 引擎,性能肯定是最好的,但它引擎體積就很大,內(nèi)存占用也很高;在包體積上很占優(yōu)勢(shì)的 QuickJS,由于沒有 JIT 加持,和有 JIT 的引擎比起來平均會(huì)有 5-10 倍的性能差距。
下面我會(huì)綜合剛剛提到的幾個(gè)點(diǎn),并選擇了 JavaScriptCore,V8,Hermes 和 QuickJS 這 4 個(gè) JSVM,說說它們的優(yōu)點(diǎn)和特點(diǎn),再談?wù)勊麄兊牟蛔恪?/p>
JS 引擎功能大比拼
1.JavaScriptCore
mobile_JSVM_JSC
JavaScriptCore 是 WebKit 默認(rèn)的內(nèi)嵌 JS 引擎,wikipedia 上都沒有獨(dú)立的詞條,只在 WebKit 詞條的三級(jí)目錄[1]里介紹了一下,個(gè)人感覺還是有些不像話,畢竟也是老牌 JS 引擎了。
由于 WebKit 是 Apple 率先開源的,所以 WebKit 引擎運(yùn)用在 Apple 自家的 Safari 瀏覽器和 WebView 上,尤其是 iOS 系統(tǒng)上,因?yàn)?Apple 的限制,所有的網(wǎng)頁只能用 WebKit 加載,所以 WebKit 在 iOS 上達(dá)到了事實(shí)壟斷,作為 WebKit 模塊一部分的 JSC,順著政策春風(fēng),也「基本」壟斷了 iOS 平臺(tái)的 JS 引擎份額。
壟斷歸壟斷,其實(shí) JSC 的性能還是可以的。
很多人不知道 JSC 的 JIT 功能其實(shí)比 V8 還要早,放在十幾年前是最好的 JS 引擎,只不過后來被 V8 追了上來。而且 JSC 有個(gè)重大利好,在 iOS7 之后,JSC 作為一個(gè)系統(tǒng)級(jí)的 Framework 開放給開發(fā)者使用,也就是說,如果你的 APP 使用 JSC,只需要在項(xiàng)目里 import 一下,包體積是 0 開銷的!這點(diǎn)在今天討論的 JS 引擎中,JSC 是最能打的。
雖然開啟 JIT 的 JSC 性能很好,但是只限于蘋果御用的 Safari 瀏覽器和 WKWebView,只有這兩個(gè)地方 JIT 功能才是默認(rèn)開啟的,如果在項(xiàng)目里直接引入 JSC,JIT 功能是關(guān)閉的。為什么這么做呢?RednaxelaFX 大佬[2] 給出過非常專業(yè)的解釋[3]:
JIT 編譯需要底層系統(tǒng)支持動(dòng)態(tài)代碼生成,對(duì)操作系統(tǒng)來說這意味著要支持動(dòng)態(tài)分配帶有“可寫可執(zhí)行”權(quán)限的內(nèi)存頁。當(dāng)一個(gè)應(yīng)用程序擁有請(qǐng)求分配可寫可執(zhí)行內(nèi)存頁的權(quán)限時(shí),它會(huì)比較容易受到攻擊從而允許任意代碼動(dòng)態(tài)生成并執(zhí)行,這樣就讓惡意代碼更容易有機(jī)可乘。
Apple 出于安全上的考慮,禁止了第三方 APP 使用 JSC 時(shí)開啟 JIT,這些特點(diǎn)在 React Native 的 JS Runtime 頁面[4]也有過相關(guān)的解釋。不過在實(shí)際應(yīng)用中,不做重 CPU 的運(yùn)算只當(dāng)膠水語言使用,JSC 還是綽綽有余了。
上面的討論都是針對(duì) iOS 系統(tǒng)的,在 Android 系統(tǒng)上,JSC 的表現(xiàn)就不盡人意了。
JSC 并沒有對(duì) Android 機(jī)型做很好的適配,雖然可以開啟 JIT,但是性能表現(xiàn)并不好,這也是 Facebook 決心制作 Hermes 的一個(gè)原因,具體的性能對(duì)比分析可見本文的 Hermes 小節(jié)。
最后再說說 JSC 的調(diào)試支持情況。如果是 iOS 平臺(tái),我們可以直接用 Safari 的 debbuger 功能調(diào)試,如果是 Android 平臺(tái),目前我還沒有找到一個(gè)很好的真機(jī)調(diào)試方法。
綜合來看,JavaScriptCore 在 iOS 平臺(tái)上有非常明顯的主場(chǎng)優(yōu)勢(shì),各個(gè)指標(biāo)都是很優(yōu)秀的,但在 Android 上因?yàn)槿狈?yōu)化,表現(xiàn)并不是很好。
2.V8
mobile_JSVM_V8
V8,我想我不用過多解釋了,JavaScript 能有如今的地位,V8 功不可沒。性能沒得說,開啟 JIT 后就是業(yè)內(nèi)最強(qiáng)(不止是 JS),有很多介紹 V8 的文章,我這里就不多描述了,我們這里說說 V8 在移動(dòng)端的表現(xiàn)。
同樣作為 Google 家的產(chǎn)品,每一臺(tái) Android 手機(jī)上都安裝了基于 Chromium 的 WebView,V8 也一并捆綁了。但是 V8 和 Chromium 捆綁的太緊密了,不像 iOS 上的 JavaScriptCore 封裝為系統(tǒng)庫可以被所有 App 調(diào)用。這就導(dǎo)致你想在 Android 上用 V8 還得自己封裝,社區(qū)比較出名的項(xiàng)目是 J2V8[5],提供了 V8 的 Java bindings 案例。
V8 性能沒得說,Android 上可以開啟 JIT,但這些優(yōu)勢(shì)都是有代價(jià)的:開啟 JIT 后內(nèi)存占用高,并且 V8 的包體積也不小(大概 7 MB 左右),如果作為只是畫 UI 的 Hybrid 系統(tǒng),還是有些奢侈了。
我們?cè)僬f說 V8 在 iOS 上的集成。
V8 在 2019 年推出了 JIT-less V8[6],也就是關(guān)閉 JIT 只使用 Ignition interpreter 解釋執(zhí)行 JS 文件,那么我們?cè)?iOS 上集成 V8 就成了可能,因?yàn)?Apple 還是支持接入只有解釋器功能的虛擬機(jī)引擎的。但是個(gè)人認(rèn)為關(guān)閉了 JIT 的 V8 接入 iOS 價(jià)值不大,因?yàn)橹婚_啟解釋器的話,這時(shí)候的 V8 和 JSC 的性能其實(shí)是差不多的,引入反而會(huì)增加一定的體積開銷。
V8 還有一個(gè)有意思的特性很少人提及,那就是——堆快照(Heap snapshots),這個(gè)是 V8 在 2015[7] 年就支持的功能,但是社區(qū)里很少有人討論它。
堆快照是什么原理呢?一般來說 JSVM 啟動(dòng)后,第一步往往是解析 JS 文件,這個(gè)還是比較耗時(shí)的,V8 支持預(yù)先生成 Heap snapshots,然后直接加載到堆內(nèi)存中,快速的獲得 JS 的初始化上下文??缙脚_(tái)框架 NativeScript[8] 就利用了這樣的技術(shù),可以讓 JS 的加載速度提升 3 倍,技術(shù)細(xì)節(jié)可以看他們的博文[9]。
V8_heap_snapshots
V8 真機(jī)調(diào)試也需要引入第三方庫,Android 端社區(qū)上有人對(duì) J2V8 做了 Chrome 調(diào)試協(xié)議的擴(kuò)展,即 J2V8-Debugger[10] 項(xiàng)目,iOS 我沒有找到相關(guān)的項(xiàng)目,可能需要自己實(shí)現(xiàn)一套擴(kuò)展。
綜合來看 V8 的確是 JSVM 中的性能王者,Android 端使用時(shí)可以完全發(fā)揮它的威力,但是 iOS 平臺(tái)因?yàn)橹鲌?chǎng)劣勢(shì),并不是很推薦。
3.Hermes
mobile_JSVM_hermes
Hermes 是 FaceBook 2019 年中旬開源的一款 JS 引擎,從 release[11] 記錄可以看出,這個(gè)是專為 React Native 打造的 JS 引擎,可以說從設(shè)計(jì)之初就是為 Hybrid UI 系統(tǒng)打造。
Hermes 一開始推出就是要替代原來 RN Android 端的 JS 引擎,即 JavaScriptCore(因?yàn)?JSC 在 Android 端表現(xiàn)太拉垮了)。我們可以理一下時(shí)間線,F(xiàn)aceBook 自從 2019-07-12 宣布 Hermes 開源[12]后,jsc-android[13] 的維護(hù)信息就永遠(yuǎn)的停在了 2019-06-25[14],這個(gè)信號(hào)暗示得非常的明顯:JavaScriptCore Android 我們不再維護(hù)啦,大家都去用我們做的 Hermes 啊。
最近 Hermes 已經(jīng)計(jì)劃伴隨 React Native 0.64 版本登錄 iOS 平臺(tái)了,但是 RN 版本更新 blog 還沒有出,大家可以看看我之前對(duì) Apple 開發(fā)者協(xié)議的解讀:Apple Agreement 3.3.2 規(guī)范解讀,在這里我就不多說了。
Hermes 的特點(diǎn)主要是兩個(gè),一個(gè)是不支持 JIT,一個(gè)是支持直接生成/加載字節(jié)碼,我們?cè)谙旅娣珠_講一下。
Hermes 不支持 JIT 的主要原因有兩個(gè):加入 JIT 后,JS 引擎啟動(dòng)的預(yù)熱時(shí)間會(huì)變長(zhǎng),一定程度上會(huì)加長(zhǎng)首屏 TTI[15](頁面首次加載可交互時(shí)間),現(xiàn)在的前端頁面都講究一個(gè)秒開,TTI 還是個(gè)挺重要的測(cè)量指標(biāo)。另一個(gè)問題上 JIT 會(huì)增加包體積和內(nèi)存占用,Chrome 內(nèi)存占用高 V8 還是要承擔(dān)一定責(zé)任的。
因?yàn)椴恢С?JIT,Hermes 在一些 CPU 密集計(jì)算的領(lǐng)域就不占優(yōu)勢(shì)了,所以在 Hybrid 系統(tǒng)里,最優(yōu)的解決方案就是充分發(fā)揮 JavaScript 膠水語言的作用,CPU 密集的計(jì)算(例如矩陣變換,參數(shù)加密等)放在 Native 里做,算好了再傳遞給 JS 表現(xiàn)在 UI 上,這樣可以兼顧性能和開發(fā)效率。
Hermes 最引人矚目的就是支持生成字節(jié)碼了,我在之前的博文《🎯 跨端框架的核心技術(shù)到底是什么?》也提到過,Hermes 加入 AOT 后,Babel、Minify、Parse 和 Compile 這些流程全部都在開發(fā)者電腦上完成,直接下發(fā)字節(jié)碼讓 Hermes 運(yùn)行就行,我們直接用個(gè) demo 演示一下。
Hermes
先寫個(gè) test.js 的文件,里面隨便寫點(diǎn)啥都行;然后編譯一下 Hermes 的源碼,編譯過程直接按文檔[16]來就行,我這里就略過了。
首先 Hermes 支持直接解釋運(yùn)行 JS 代碼,就是正常的 JS 加載編譯運(yùn)行流程。
- hermes test.js
我們可以加入 -emit-binary 參數(shù)嘗試一下生成 Bytecode 的功能:
- hermes -emit-binary -out test.hbc test.js
然后就會(huì)生成一份 test.hbc 字節(jié)碼文件:
hermes_bytecode
最后我們可以讓 Hermes 直接加載運(yùn)行 test.hbc 文件:
- hermes test.hbc
客觀評(píng)價(jià)一下 Hermes 的字節(jié)碼,首先省去了在 JS 引擎里解析編譯的流程,JS 代碼的加載速度將會(huì)大大加快,體現(xiàn)在 UI 上就是 TTI 時(shí)間會(huì)明顯縮短;另一個(gè)優(yōu)勢(shì) Hermes 的字節(jié)碼在設(shè)計(jì)時(shí)就考慮了移動(dòng)端的性能限制,支持增量加載而不是全量加載,對(duì)內(nèi)存受限的中低端 Android 機(jī)更友好;不過字節(jié)碼的體積會(huì)比原來的 JS 文件會(huì)大一些,但是考慮到 Hermes 引擎本身體積就不大,綜合考慮下來這些體積增量還是可以接受的。
關(guān)于詳細(xì)的 Hermes 性能測(cè)試情況,網(wǎng)上有兩篇文章寫的比較好:一篇是 React Native Memory profiling: JSC vs V8 vs Hermes[17],可以看到在 Android 設(shè)備上 Hermes 的表現(xiàn)還是很優(yōu)異的,而 JSC 的表現(xiàn)非常拉垮:
JSCvsV8vsHermes
另一篇是攜程的文章:攜程對(duì) RN 新一代 JS 引擎 Hermes 的調(diào)研,可以看出 Hermes 綜合成績(jī)最高(JSC 還是一樣的拉垮):
JSVM_CPU_Performance
說完性能我們?cè)僬f說 Hermes 的 JS 語法支持情況。
Hermes 主要支持的是 ES6 語法,剛開源時(shí)不支持 Proxy,不過 v0.7.0[18] 已經(jīng)支持了。他們的團(tuán)隊(duì)也比較有想法,不支持 with eval() 等這種屬于設(shè)計(jì)糟粕的 API,這種設(shè)計(jì)的權(quán)衡我個(gè)人還是比較認(rèn)同的。
最后我們談?wù)?Hermes 的調(diào)試功能。
目前 Hermes 已經(jīng)支持了 Chrome 的調(diào)試協(xié)議,我們可以直接用 Chrome 的 debugging 工具直接調(diào)試 Hermes 引擎,具體的操作可見文檔:Debugging JS on Hermes using Google Chrome's DevTools[19]
綜合來看,Hermes 是一款專為移動(dòng)端 Hybrid UI System 打造的 JS 引擎,如果要自建一套 Hybrid 系統(tǒng),Hermes 是一個(gè)非常好的選擇。
4.QuickJS
mobile_JSVM_quickjs
正式介紹 QuickJS 前我們先說說它的作者:Fabrice Bellard。
軟件界一直有個(gè)說法,一個(gè)高級(jí)程序員創(chuàng)造的價(jià)值可以超過 20 個(gè)平庸的程序員,但 Fabrice Bellard 不是高級(jí)程序員,他是天才,在我看來他的創(chuàng)造力可以超過 20 個(gè)高級(jí)程序員,我們可以順著時(shí)間軸[20]理一下他創(chuàng)造過些什么:
- 1997年,發(fā)布了最快速的計(jì)算圓周率的算法,此算法是 Bailey-Borwein-Plouffe 公式的變體,前者的時(shí)間復(fù)雜度是O(n^3),他給優(yōu)化成了O(n^2),使得計(jì)算速度提高了43%,這是他在數(shù)學(xué)上的成就
- 2000 年,發(fā)布了 FFmpeg,這是他在音視頻領(lǐng)域的一個(gè)成就
- 2000,2001,2018 三年三度獲得國(guó)際混淆 C 代碼大賽
- 2002 年,發(fā)布了TinyGL,這是他在圖形學(xué)領(lǐng)域的成就
- 2005 年,發(fā)布了 QEMU,這是他在虛擬化領(lǐng)域的成就
- 2011 年,他用 JavaScript 寫了一個(gè) PC 虛擬機(jī) Jslinux,一個(gè)跑在瀏覽器上的 Linux 操作系統(tǒng)
- 2019 年,發(fā)布了 QuickJS,一個(gè)支持 ES2020 規(guī)范的 JS 虛擬機(jī)
當(dāng)人和人之間的差距差了幾個(gè)數(shù)量級(jí)后,羨慕嫉妒之類的情緒就會(huì)轉(zhuǎn)變?yōu)槌绨萘?,Bellard 就是一個(gè)這樣的人。
收復(fù)一下心情,我們來看一下 QuickJS 這個(gè)項(xiàng)目。QuickJS 繼承了 Fabrice Bellard 作品的一貫特色——小巧而又強(qiáng)大。
QuickJS 體積非常小,只有幾個(gè) C 文件,沒有亂七八糟的第三方依賴。但是他的功能又非常完善,JS 語法支持到 ES2020[21],Test262[22] 的測(cè)試顯示,QuickJS 的語法支持度比 V8 還要高。
test262
那么 QuickJS 的性能如何呢?QuickJS 官上有個(gè)基準(zhǔn)測(cè)試[23],綜合比較了多款 JS 引擎對(duì)同一測(cè)試用例的跑分情況。下面是測(cè)試結(jié)果:
JSVM_Benchmark
結(jié)合上面的表格和個(gè)人的一些測(cè)試,可以簡(jiǎn)單的得出一些結(jié)論:
- 開啟 JIT 的 V8 綜合評(píng)分差不多是 QuickJS 的 35 倍,但是在同等主打輕量的 JS 引擎中,QuickJS 的性能還是很耀眼的
- 在內(nèi)存占用上,QuickJS 遠(yuǎn)低于 V8,畢竟 JIT 是是吃內(nèi)存的大戶,而且 QuickJS 的設(shè)計(jì)對(duì)嵌入式系統(tǒng)很友好(Bellard 成就獎(jiǎng)杯 🏆 再 +1)
- QuickJS 和 Hermes 的跑分情況是差不多的,我私下做了一些性能測(cè)試,這兩個(gè)引擎的表現(xiàn)也很相近
因?yàn)?QuickJS 的設(shè)計(jì),我不經(jīng)好奇他和 Lua 的性能對(duì)比如何。
Lua 是一門非常小巧精悍的語言,在游戲領(lǐng)域和 C/C++ 開發(fā)中一直充當(dāng)膠水語言的作用。
我個(gè)人寫了一些測(cè)試用例,發(fā)現(xiàn) QuickJS 和 Lua 的執(zhí)行效率也是差不多的,后來在網(wǎng)上找到一篇博文 Lua vs QuickJS[24],這個(gè)老哥也做了一些測(cè)試,結(jié)論也是它倆的性能差不多,在部分場(chǎng)景 Lua 會(huì)比 QuickJS 快一些。
官方文檔里有提到,QuickJS 支持生成字節(jié)碼[25],這樣可以免去 JS 文件編譯解析的過程。
我一開始以為 QuickJS 和 Hermes 一樣,可以直接生成字節(jié)碼,然后交給 QuickJS 解釋執(zhí)行。后來自己編譯了一下才發(fā)現(xiàn),QuickJS 的作用機(jī)制和 Hermes 還不太一樣:qjsc 生成字節(jié)碼的 -e 和 -c 選項(xiàng),都是先把 js 文件生成一份字節(jié)碼,然后拼到一個(gè) .c 文件里,大概長(zhǎng)下面的這個(gè)樣子:
- #include <quickjs/quickjs-libc.h>
- const uint32_t qjsc_hello_size = 87;
- // JS 文件編譯生成的字節(jié)碼都在這個(gè)數(shù)組里
- const uint8_t qjsc_hello[87] = {
- 0x02, 0x04, 0x0e, 0x63, 0x6f, 0x6e, 0x73, 0x6f,
- 0x6c, 0x65, 0x06, 0x6c, 0x6f, 0x67, 0x16, 0x48,
- 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72,
- 0x6c, 0x64, 0x22, 0x65, 0x78, 0x61, 0x6d, 0x70,
- 0x6c, 0x65, 0x73, 0x2f, 0x68, 0x65, 0x6c, 0x6c,
- 0x6f, 0x2e, 0x6a, 0x73, 0x0e, 0x00, 0x06, 0x00,
- 0x9e, 0x01, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00,
- 0x14, 0x01, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x39,
- 0xf1, 0x00, 0x00, 0x00, 0x43, 0xf2, 0x00, 0x00,
- 0x00, 0x04, 0xf3, 0x00, 0x00, 0x00, 0x24, 0x01,
- 0x00, 0xd1, 0x28, 0xe8, 0x03, 0x01, 0x00,
- };
- int main(int argc, char **argv)
- {
- JSRuntime *rt;
- JSContext *ctx;
- rt = JS_NewRuntime();
- ctx = JS_NewContextRaw(rt);
- JS_AddIntrinsicBaseObjects(ctx);
- js_std_add_helpers(ctx, argc, argv);
- js_std_eval_binary(ctx, qjsc_hello, qjsc_hello_size, 0);
- js_std_loop(ctx);
- JS_FreeContext(ctx);
- JS_FreeRuntime(rt);
- return 0;
- }
因?yàn)檫@是個(gè) .c 文件,想跑起來還得再編譯一次生成二進(jìn)制文件。
從字節(jié)碼這個(gè)設(shè)計(jì)點(diǎn)來看,QuickJS 和 Hermes 的定位還是不太一樣的。
雖然直接生成字節(jié)碼可以大大減少 JS 文本文件的解析時(shí)間,但是 QuickJS 還是更偏嵌入式一些,生成的字節(jié)碼放在一個(gè) C 文件中,還需要進(jìn)行編譯才能運(yùn)行;Hermes 為 React Native 而生,生成的字節(jié)碼一開始就考慮到分發(fā)功能(熱更新就是一個(gè)應(yīng)用場(chǎng)景),支持字節(jié)碼的直接加載運(yùn)行,不需要再編譯一次。
上面主要還是對(duì)性能的考量,下面我們看看開發(fā)體驗(yàn)。
首先是 QuickJS 的調(diào)試功能支持。到目前為止(2021-02-22),QuickJS 還沒有官方的調(diào)試器,也就是說 debugger 語句會(huì)被忽略,社區(qū)有人實(shí)現(xiàn)了一套基于 VSCode 的調(diào)試器支持vscode-quickjs-debug[26],但是會(huì)對(duì) QuickJS 做一些定制,個(gè)人還是蠻期待官方支持某個(gè)調(diào)試器協(xié)議的。
從 集成 的角度上看,社區(qū)上已經(jīng)有了 iOS[27] 和 Android[28] 的示例項(xiàng)目,可以拿來用來參考接入到自己的工程中。
綜合來看,QuickJS 是一款潛力非常大的 JS 引擎,在 JS 語法高度支持的前提下,還把性能和體積都優(yōu)化到了極致。在移動(dòng)端的 Hybrid UI 架構(gòu)和游戲腳本系統(tǒng)都可以考慮接入。
選型思路
1.單引擎
單引擎的意思就是 iOS 端和 Android 端統(tǒng)一采用一個(gè)引擎,這樣做的話在 JS 層差異可以抹平,不容易出現(xiàn)同一份 JS 代碼在 iOS 上運(yùn)行是好的,Android 上就出錯(cuò)的奇異 BUG。結(jié)合市面上的跨端方案,大概有下面三種選型:
- 統(tǒng)一采用 JSC:這個(gè)是 React Native 0.60 之前的方案
- 統(tǒng)一使用 Hermes:這個(gè)是 React Native 0.64 之后的設(shè)計(jì)方案
- 統(tǒng)一采用 QuickJS:QuickJS 體積很小,可以用來制作非常輕量的 Hybrid 系統(tǒng)
上面看出沒有統(tǒng)一采用 V8,這個(gè)就是我前面說的,V8 在 iOS 平臺(tái)沒有主場(chǎng)優(yōu)勢(shì),關(guān)閉 JIT 后性能和 JSC 差不多,還會(huì)增大包體積,并不是很劃算。
2.雙引擎
雙引擎也很好理解,就是 iOS 端和 Android 端各用各的,優(yōu)點(diǎn)是可以發(fā)揮各自的主場(chǎng)優(yōu)勢(shì),缺點(diǎn)是可能會(huì)因?yàn)槠脚_(tái)不一致導(dǎo)致雙端運(yùn)行結(jié)果不統(tǒng)一,現(xiàn)在的方案有這么幾種:
- iOS 用 JSC,Android 用 V8:Weex,NativeScript 都是這樣的,可以在包體積和性能上有較好的均衡
- iOS 用 JSC,Android 用 Hermes:React Natvie 現(xiàn)如今的方案
- iOS 用 JSC,Android 用 QuickJS:滴滴的跨端框架 hummer[29] 就是這樣的設(shè)計(jì)
從選型上看,iOS 上都選擇了 JSC,Android 各有各的選擇,倒是充分發(fā)揮了兩個(gè)平臺(tái)的特色 : )
3.調(diào)試
無論是單引擎還是雙引擎,集成后的業(yè)務(wù)開發(fā)體驗(yàn)也很重要。對(duì)于自帶 debugger 功能的引擎來說一切都不在話下,但是對(duì)于沒有實(shí)現(xiàn)調(diào)試協(xié)議的引擎來說,缺少 debugger 還是會(huì)影響體驗(yàn)的。
但不是也沒有辦法,一般來說我們可以曲線救國(guó),類似于 React Native 的 Remote JS Debugging 的思路:
我們可以加個(gè)開關(guān),把 JS 代碼通過 websocket 傳送到 Chrome 的 Web Worker,然后用 Chrome 的 V8 進(jìn)行調(diào)試。這樣做的優(yōu)勢(shì)是可以調(diào)整一些業(yè)務(wù)上的 BUG,劣勢(shì)就是又會(huì)引入一個(gè) JS 引擎,萬一遇到一些引擎實(shí)現(xiàn)的 BUG,就很難 debug 了。不過好在這種情況非常非常少見,我們也不能因噎廢食對(duì)吧。
總結(jié)
本文從性能、體積、調(diào)試便捷性等功能點(diǎn)出發(fā),分析了 JavaScriptCore,V8,Hermes 和QuickJS 這 4 款 JS 引擎,分別分析了它們的缺點(diǎn)和弱點(diǎn)。如果大家有移動(dòng)端 JS 引擎選型的困惑,我認(rèn)為從本文出發(fā),還是可以給不少人以靈感的,希望我的這篇文章能幫助到大家。
參考鏈接跨端框架的核心技術(shù)到底是什么?
如何隱藏你的熱更新 bundle 文件?
深入理解JSCore[30]
QuickJS 引擎一年見聞錄[31]