您好,登錄后才能下訂單哦!
這篇文章主要介紹“移動端JS的引擎有哪些”,在日常操作中,相信很多人在移動端JS的引擎有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”移動端JS的引擎有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
JS 引擎選型要點
JavaScript 作為世界上最熱門的腳本語言,有著非常多的引擎實現:有 Apple 御用的 JavaScriptCore,有性能最強勁的 V8,還有最近熱度很高的 QuickJS......如何從這些 JS 引擎里選出最適合的?我個人認為要有幾個考量:
性能:這個沒話說,肯定是越快越好
體積:JS 引擎會增加一定的包體積
內存占用:內存占用越少越好
JavaScript 語法支持程度:支持的新語法越多越好
調試的便捷性:是否直接支持 debug?還是需要自己編譯實現調試工具鏈
應用市場平臺規范:主要是 iOS 平臺,平臺禁止應用集成帶 JIT 功能的虛擬機
比較麻煩的是,上面的幾個點都不是互相獨立的:
比如說開啟 JIT 的 V8 引擎,性能肯定是最好的,但它引擎體積就很大,內存占用也很高;在包體積上很占優勢的 QuickJS,由于沒有 JIT 加持,和有 JIT 的引擎比起來平均會有 5-10 倍的性能差距。
下面我會綜合剛剛提到的幾個點,并選擇了 JavaScriptCore,V8,Hermes 和 QuickJS 這 4 個 JSVM,說說它們的優點和特點,再談談他們的不足。
JS 引擎功能大比拼
1.JavaScriptCore
mobile_JSVM_JSC
JavaScriptCore 是 WebKit 默認的內嵌 JS 引擎,wikipedia 上都沒有獨立的詞條,只在 WebKit 詞條的三級目錄[1]里介紹了一下,個人感覺還是有些不像話,畢竟也是老牌 JS 引擎了。
由于 WebKit 是 Apple 率先開源的,所以 WebKit 引擎運用在 Apple 自家的 Safari 瀏覽器和 WebView 上,尤其是 iOS 系統上,因為 Apple 的限制,所有的網頁只能用 WebKit 加載,所以 WebKit 在 iOS 上達到了事實壟斷,作為 WebKit 模塊一部分的 JSC,順著政策春風,也「基本」壟斷了 iOS 平臺的 JS 引擎份額。
壟斷歸壟斷,其實 JSC 的性能還是可以的。
很多人不知道 JSC 的 JIT 功能其實比 V8 還要早,放在十幾年前是最好的 JS 引擎,只不過后來被 V8 追了上來。而且 JSC 有個重大利好,在 iOS7 之后,JSC 作為一個系統級的 Framework 開放給開發者使用,也就是說,如果你的 APP 使用 JSC,只需要在項目里 import 一下,包體積是 0 開銷的!這點在今天討論的 JS 引擎中,JSC 是最能打的。
雖然開啟 JIT 的 JSC 性能很好,但是只限于蘋果御用的 Safari 瀏覽器和 WKWebView,只有這兩個地方 JIT 功能才是默認開啟的,如果在項目里直接引入 JSC,JIT 功能是關閉的。為什么這么做呢?RednaxelaFX 大佬[2] 給出過非常專業的解釋[3]:
JIT 編譯需要底層系統支持動態代碼生成,對操作系統來說這意味著要支持動態分配帶有“可寫可執行”權限的內存頁。當一個應用程序擁有請求分配可寫可執行內存頁的權限時,它會比較容易受到攻擊從而允許任意代碼動態生成并執行,這樣就讓惡意代碼更容易有機可乘。
Apple 出于安全上的考慮,禁止了第三方 APP 使用 JSC 時開啟 JIT,這些特點在 React Native 的 JS Runtime 頁面[4]也有過相關的解釋。不過在實際應用中,不做重 CPU 的運算只當膠水語言使用,JSC 還是綽綽有余了。
上面的討論都是針對 iOS 系統的,在 Android 系統上,JSC 的表現就不盡人意了。
JSC 并沒有對 Android 機型做很好的適配,雖然可以開啟 JIT,但是性能表現并不好,這也是 Facebook 決心制作 Hermes 的一個原因,具體的性能對比分析可見本文的 Hermes 小節。
最后再說說 JSC 的調試支持情況。如果是 iOS 平臺,我們可以直接用 Safari 的 debbuger 功能調試,如果是 Android 平臺,目前我還沒有找到一個很好的真機調試方法。
綜合來看,JavaScriptCore 在 iOS 平臺上有非常明顯的主場優勢,各個指標都是很優秀的,但在 Android 上因為缺乏優化,表現并不是很好。
2.V8
mobile_JSVM_V8
V8,我想我不用過多解釋了,JavaScript 能有如今的地位,V8 功不可沒。性能沒得說,開啟 JIT 后就是業內最強(不止是 JS),有很多介紹 V8 的文章,我這里就不多描述了,我們這里說說 V8 在移動端的表現。
同樣作為 Google 家的產品,每一臺 Android 手機上都安裝了基于 Chromium 的 WebView,V8 也一并捆綁了。但是 V8 和 Chromium 捆綁的太緊密了,不像 iOS 上的 JavaScriptCore 封裝為系統庫可以被所有 App 調用。這就導致你想在 Android 上用 V8 還得自己封裝,社區比較出名的項目是 J2V8[5],提供了 V8 的 Java bindings 案例。
V8 性能沒得說,Android 上可以開啟 JIT,但這些優勢都是有代價的:開啟 JIT 后內存占用高,并且 V8 的包體積也不小(大概 7 MB 左右),如果作為只是畫 UI 的 Hybrid 系統,還是有些奢侈了。
我們再說說 V8 在 iOS 上的集成。
V8 在 2019 年推出了 JIT-less V8[6],也就是關閉 JIT 只使用 Ignition interpreter 解釋執行 JS 文件,那么我們在 iOS 上集成 V8 就成了可能,因為 Apple 還是支持接入只有解釋器功能的虛擬機引擎的。但是個人認為關閉了 JIT 的 V8 接入 iOS 價值不大,因為只開啟解釋器的話,這時候的 V8 和 JSC 的性能其實是差不多的,引入反而會增加一定的體積開銷。
V8 還有一個有意思的特性很少人提及,那就是——堆快照(Heap snapshots),這個是 V8 在 2015[7] 年就支持的功能,但是社區里很少有人討論它。
堆快照是什么原理呢?一般來說 JSVM 啟動后,第一步往往是解析 JS 文件,這個還是比較耗時的,V8 支持預先生成 Heap snapshots,然后直接加載到堆內存中,快速的獲得 JS 的初始化上下文。跨平臺框架 NativeScript[8] 就利用了這樣的技術,可以讓 JS 的加載速度提升 3 倍,技術細節可以看他們的博文[9]。
V8_heap_snapshots
V8 真機調試也需要引入第三方庫,Android 端社區上有人對 J2V8 做了 Chrome 調試協議的擴展,即 J2V8-Debugger[10] 項目,iOS 我沒有找到相關的項目,可能需要自己實現一套擴展。
綜合來看 V8 的確是 JSVM 中的性能王者,Android 端使用時可以完全發揮它的威力,但是 iOS 平臺因為主場劣勢,并不是很推薦。
3.Hermes
mobile_JSVM_hermes
Hermes 是 FaceBook 2019 年中旬開源的一款 JS 引擎,從 release[11] 記錄可以看出,這個是專為 React Native 打造的 JS 引擎,可以說從設計之初就是為 Hybrid UI 系統打造。
Hermes 一開始推出就是要替代原來 RN Android 端的 JS 引擎,即 JavaScriptCore(因為 JSC 在 Android 端表現太拉垮了)。我們可以理一下時間線,FaceBook 自從 2019-07-12 宣布 Hermes 開源[12]后,jsc-android[13] 的維護信息就永遠的停在了 2019-06-25[14],這個信號暗示得非常的明顯:JavaScriptCore Android 我們不再維護啦,大家都去用我們做的 Hermes 啊。
最近 Hermes 已經計劃伴隨 React Native 0.64 版本登錄 iOS 平臺了,但是 RN 版本更新 blog 還沒有出,大家可以看看我之前對 Apple 開發者協議的解讀:Apple Agreement 3.3.2 規范解讀,在這里我就不多說了。
Hermes 的特點主要是兩個,一個是不支持 JIT,一個是支持直接生成/加載字節碼,我們在下面分開講一下。
Hermes 不支持 JIT 的主要原因有兩個:加入 JIT 后,JS 引擎啟動的預熱時間會變長,一定程度上會加長首屏 TTI[15](頁面首次加載可交互時間),現在的前端頁面都講究一個秒開,TTI 還是個挺重要的測量指標。另一個問題上 JIT 會增加包體積和內存占用,Chrome 內存占用高 V8 還是要承擔一定責任的。
因為不支持 JIT,Hermes 在一些 CPU 密集計算的領域就不占優勢了,所以在 Hybrid 系統里,最優的解決方案就是充分發揮 JavaScript 膠水語言的作用,CPU 密集的計算(例如矩陣變換,參數加密等)放在 Native 里做,算好了再傳遞給 JS 表現在 UI 上,這樣可以兼顧性能和開發效率。
Hermes 最引人矚目的就是支持生成字節碼了,我在之前的博文《? 跨端框架的核心技術到底是什么?》也提到過,Hermes 加入 AOT 后,Babel、Minify、Parse 和 Compile 這些流程全部都在開發者電腦上完成,直接下發字節碼讓 Hermes 運行就行,我們直接用個 demo 演示一下。
Hermes
先寫個 test.js 的文件,里面隨便寫點啥都行;然后編譯一下 Hermes 的源碼,編譯過程直接按文檔[16]來就行,我這里就略過了。
首先 Hermes 支持直接解釋運行 JS 代碼,就是正常的 JS 加載編譯運行流程。
hermes test.js
我們可以加入 -emit-binary 參數嘗試一下生成 Bytecode 的功能:
hermes -emit-binary -out test.hbc test.js
然后就會生成一份 test.hbc 字節碼文件:
hermes_bytecode
最后我們可以讓 Hermes 直接加載運行 test.hbc 文件:
hermes test.hbc
客觀評價一下 Hermes 的字節碼,首先省去了在 JS 引擎里解析編譯的流程,JS 代碼的加載速度將會大大加快,體現在 UI 上就是 TTI 時間會明顯縮短;另一個優勢 Hermes 的字節碼在設計時就考慮了移動端的性能限制,支持增量加載而不是全量加載,對內存受限的中低端 Android 機更友好;不過字節碼的體積會比原來的 JS 文件會大一些,但是考慮到 Hermes 引擎本身體積就不大,綜合考慮下來這些體積增量還是可以接受的。
關于詳細的 Hermes 性能測試情況,網上有兩篇文章寫的比較好:一篇是 React Native Memory profiling: JSC vs V8 vs Hermes[17],可以看到在 Android 設備上 Hermes 的表現還是很優異的,而 JSC 的表現非常拉垮:
JSCvsV8vsHermes
另一篇是攜程的文章:攜程對 RN 新一代 JS 引擎 Hermes 的調研,可以看出 Hermes 綜合成績最高(JSC 還是一樣的拉垮):
JSVM_CPU_Performance
說完性能我們再說說 Hermes 的 JS 語法支持情況。
Hermes 主要支持的是 ES6 語法,剛開源時不支持 Proxy,不過 v0.7.0[18] 已經支持了。他們的團隊也比較有想法,不支持 with eval() 等這種屬于設計糟粕的 API,這種設計的權衡我個人還是比較認同的。
最后我們談談 Hermes 的調試功能。
目前 Hermes 已經支持了 Chrome 的調試協議,我們可以直接用 Chrome 的 debugging 工具直接調試 Hermes 引擎,具體的操作可見文檔:Debugging JS on Hermes using Google Chrome's DevTools[19]
綜合來看,Hermes 是一款專為移動端 Hybrid UI System 打造的 JS 引擎,如果要自建一套 Hybrid 系統,Hermes 是一個非常好的選擇。
4.QuickJS
mobile_JSVM_quickjs
正式介紹 QuickJS 前我們先說說它的作者:Fabrice Bellard。
軟件界一直有個說法,一個高級程序員創造的價值可以超過 20 個平庸的程序員,但 Fabrice Bellard 不是高級程序員,他是天才,在我看來他的創造力可以超過 20 個高級程序員,我們可以順著時間軸[20]理一下他創造過些什么:
1997年,發布了最快速的計算圓周率的算法,此算法是 Bailey-Borwein-Plouffe 公式的變體,前者的時間復雜度是O(n^3),他給優化成了O(n^2),使得計算速度提高了43%,這是他在數學上的成就
2000 年,發布了 FFmpeg,這是他在音視頻領域的一個成就
2000,2001,2018 三年三度獲得國際混淆 C 代碼大賽
2002 年,發布了TinyGL,這是他在圖形學領域的成就
2005 年,發布了 QEMU,這是他在虛擬化領域的成就
2011 年,他用 JavaScript 寫了一個 PC 虛擬機 Jslinux,一個跑在瀏覽器上的 Linux 操作系統
2019 年,發布了 QuickJS,一個支持 ES2020 規范的 JS 虛擬機
當人和人之間的差距差了幾個數量級后,羨慕嫉妒之類的情緒就會轉變為崇拜了,Bellard 就是一個這樣的人。
收復一下心情,我們來看一下 QuickJS 這個項目。QuickJS 繼承了 Fabrice Bellard 作品的一貫特色——小巧而又強大。
QuickJS 體積非常小,只有幾個 C 文件,沒有亂七八糟的第三方依賴。但是他的功能又非常完善,JS 語法支持到 ES2020[21],Test262[22] 的測試顯示,QuickJS 的語法支持度比 V8 還要高。
test262
那么 QuickJS 的性能如何呢?QuickJS 官上有個基準測試[23],綜合比較了多款 JS 引擎對同一測試用例的跑分情況。下面是測試結果:
JSVM_Benchmark
結合上面的表格和個人的一些測試,可以簡單的得出一些結論:
開啟 JIT 的 V8 綜合評分差不多是 QuickJS 的 35 倍,但是在同等主打輕量的 JS 引擎中,QuickJS 的性能還是很耀眼的
在內存占用上,QuickJS 遠低于 V8,畢竟 JIT 是是吃內存的大戶,而且 QuickJS 的設計對嵌入式系統很友好(Bellard 成就獎杯 ? 再 +1)
QuickJS 和 Hermes 的跑分情況是差不多的,我私下做了一些性能測試,這兩個引擎的表現也很相近
因為 QuickJS 的設計,我不經好奇他和 Lua 的性能對比如何。
Lua 是一門非常小巧精悍的語言,在游戲領域和 C/C++ 開發中一直充當膠水語言的作用。
我個人寫了一些測試用例,發現 QuickJS 和 Lua 的執行效率也是差不多的,后來在網上找到一篇博文 Lua vs QuickJS[24],這個老哥也做了一些測試,結論也是它倆的性能差不多,在部分場景 Lua 會比 QuickJS 快一些。
官方文檔里有提到,QuickJS 支持生成字節碼[25],這樣可以免去 JS 文件編譯解析的過程。
我一開始以為 QuickJS 和 Hermes 一樣,可以直接生成字節碼,然后交給 QuickJS 解釋執行。后來自己編譯了一下才發現,QuickJS 的作用機制和 Hermes 還不太一樣:qjsc 生成字節碼的 -e 和 -c 選項,都是先把 js 文件生成一份字節碼,然后拼到一個 .c 文件里,大概長下面的這個樣子:
#include <quickjs/quickjs-libc.h> const uint32_t qjsc_hello_size = 87; // JS 文件編譯生成的字節碼都在這個數組里 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; }
因為這是個 .c 文件,想跑起來還得再編譯一次生成二進制文件。
從字節碼這個設計點來看,QuickJS 和 Hermes 的定位還是不太一樣的。
雖然直接生成字節碼可以大大減少 JS 文本文件的解析時間,但是 QuickJS 還是更偏嵌入式一些,生成的字節碼放在一個 C 文件中,還需要進行編譯才能運行;Hermes 為 React Native 而生,生成的字節碼一開始就考慮到分發功能(熱更新就是一個應用場景),支持字節碼的直接加載運行,不需要再編譯一次。
上面主要還是對性能的考量,下面我們看看開發體驗。
首先是 QuickJS 的調試功能支持。到目前為止(2021-02-22),QuickJS 還沒有官方的調試器,也就是說 debugger 語句會被忽略,社區有人實現了一套基于 VSCode 的調試器支持vscode-quickjs-debug[26],但是會對 QuickJS 做一些定制,個人還是蠻期待官方支持某個調試器協議的。
從 集成 的角度上看,社區上已經有了 iOS[27] 和 Android[28] 的示例項目,可以拿來用來參考接入到自己的工程中。
綜合來看,QuickJS 是一款潛力非常大的 JS 引擎,在 JS 語法高度支持的前提下,還把性能和體積都優化到了極致。在移動端的 Hybrid UI 架構和游戲腳本系統都可以考慮接入。
選型思路
1.單引擎
單引擎的意思就是 iOS 端和 Android 端統一采用一個引擎,這樣做的話在 JS 層差異可以抹平,不容易出現同一份 JS 代碼在 iOS 上運行是好的,Android 上就出錯的奇異 BUG。結合市面上的跨端方案,大概有下面三種選型:
統一采用 JSC:這個是 React Native 0.60 之前的方案
統一使用 Hermes:這個是 React Native 0.64 之后的設計方案
統一采用 QuickJS:QuickJS 體積很小,可以用來制作非常輕量的 Hybrid 系統
上面看出沒有統一采用 V8,這個就是我前面說的,V8 在 iOS 平臺沒有主場優勢,關閉 JIT 后性能和 JSC 差不多,還會增大包體積,并不是很劃算。
2.雙引擎
雙引擎也很好理解,就是 iOS 端和 Android 端各用各的,優點是可以發揮各自的主場優勢,缺點是可能會因為平臺不一致導致雙端運行結果不統一,現在的方案有這么幾種:
iOS 用 JSC,Android 用 V8:Weex,NativeScript 都是這樣的,可以在包體積和性能上有較好的均衡
iOS 用 JSC,Android 用 Hermes:React Natvie 現如今的方案
iOS 用 JSC,Android 用 QuickJS:滴滴的跨端框架 hummer[29] 就是這樣的設計
從選型上看,iOS 上都選擇了 JSC,Android 各有各的選擇,倒是充分發揮了兩個平臺的特色 : )
3.調試
無論是單引擎還是雙引擎,集成后的業務開發體驗也很重要。對于自帶 debugger 功能的引擎來說一切都不在話下,但是對于沒有實現調試協議的引擎來說,缺少 debugger 還是會影響體驗的。
但不是也沒有辦法,一般來說我們可以曲線救國,類似于 React Native 的 Remote JS Debugging 的思路:
我們可以加個開關,把 JS 代碼通過 websocket 傳送到 Chrome 的 Web Worker,然后用 Chrome 的 V8 進行調試。這樣做的優勢是可以調整一些業務上的 BUG,劣勢就是又會引入一個 JS 引擎,萬一遇到一些引擎實現的 BUG,就很難 debug 了。不過好在這種情況非常非常少見,我們也不能因噎廢食對吧。
到此,關于“移動端JS的引擎有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。