關注我,做個專業的技術人!
之前我在網上閒逛的時候發現了一件事,那就是兩年多來都沒人做一次高水平的 JavaScript 框架性能對比。所以在 2021 年伊始,我們就來找點樂趣,讓這些庫好好較量一下吧。本文主要出於娛樂目的,在某種意義上說,會具有一定的參考意義。該怎麼對比呢?那就選出 20 個最受歡迎的 JavaScript 框架,並用 JS Framework Benchmark 來一場針鋒相對的大比拼如何?
免責聲明:這篇評測主要是出於娛樂目的,可能也會有些實際價值。一如既往,這裡提到的每一個庫在大多數場景中效率都很高。如果有什麼需要強調的話,那就是測出的性能優勢可能來自多種不同的技術和技巧組合。雖然你可以將本文當做參考,但你應該自己去驗證框架在各個用例中的性能。你可以在這裡找到最新的官方成績對比:
https://krausest.github.io/js-framework-benchmark/index.html
如果你想進一步了解這個基準測試,可以參閱我寫的這篇指南:
https://dev.to/ryansolid/making-sense-of-the-js-framework-benchmark-25hl
還有一點需要提一下,我是 Solid Framework 的作者,因此我免不了會在文章中摻雜一些偏見。但我的打算是儘量讓數字說話。了解過這些背景後,就請坐下來欣賞比賽吧。
我用的是 JS Framework Benchmark 中最新的 Chrome 87。它們是用一部安裝 Fedora 33 系統的 Core i7 Razor Blade 15 測出來的,且緩解措施已關閉。
我排除掉了所有有問題的實現,然後選出 Github 星級最多的前 20 個庫。對於擁有多個版本的庫,我選的是它們的最新版本和沒有使用第三方庫的性能最高的分支。
Vue(177k)
React(161k)
Angular(68.9k)
Svelte(40.5k)
Preact(27.9k)
Ember(21.7k)
HyperApp(18.2k)
Inferno(14.6k)
Riot(14.4k)
Yew(14.2k)
Mithril(12.5k)
Alpine(12.4k)
Knockout(9.9k)
Marko(9.9k)
Rax(7k)
lit-html(6.9k)
Elm(6.2k)
Ractive(5.8k)
Solid(4.7k)
Imba(4.1k)
注意:對於 lit-html,我用的是 LitElement 實現,因為標準版標記了一個問題。它的開銷應該很小,因為它是包裝在單個 Web 組件中的原始 lit-html。
這是當下 Web 開發生態系統中非常優秀的一組選手。儘管 Github 星級並不能完全說明問題,但測試結果裡有 100 多個庫,因此我需要一個標準來選出參賽者。
每個庫都會參與三大類別的對比:DOM 性能、啟動指標和內存使用情況。此外,我會將這些框架分為 4 組,這樣就能更好地同性能相近的對手對比了。但我也會給出三大類別的總排名。
在每一組中都會有作為參考的標準 JavaScript 條目。這一實現使用了所有最優秀的技術來做優化,以實現最佳性能。它將作為所有對比的基線。
下面就開始比賽吧。
這一組選手最多,包括一些最流行的庫。還有一些庫有 Facebook、Google、eBay 和 Alibaba 等大公司的支持。這些庫要麼不太關注性能表現,要麼就是只關注某一方面的性能,而在其他方面表現不佳。
圖片第 4 組速度成績
這裡有很多紅色和橙色塊,但請記住,這些庫平均只比我們精心優化的命令式標準 JavaScript 參考慢大約一倍。400ms 比 200ms 能慢多少呢?
在純性能方面,React 在這一組中拔得頭籌。但考慮到架構的差異性,React、Marko、Angular 和 Ember 的整體表現竟然會如此接近,這也很讓人驚訝。不過 React,具體來說是 React Hooks 實現最後勝出。如果你需要額外的函數創建並堅持使用類,那麼就不用對性能抱太大期望了。React Hooks 是使用 React 的最高效途徑:
https://blog.usejournal.com/react-hooks-the-most-performant-way-to-write-react-393e135e1cc
這裡的庫大都有簡陋的列表排序(導致糟糕的行交換性能),或者有很高的創建成本。Ember 就是一個非常突出的典型,因為它的更新性能同組的對手要好得多,但創建性能卻是最差的。
最慢的庫(Knockout、Ractive 和 Alpine)都有著架構相似的細粒度響應庫。Knockout 和 Ractive(也是 Svelte 的作者 Rich Harris 的作品)都起源於 VDOM 庫流行之前的 2010 年代初期。Alpine 的那點 JavaScript 方法渲染起來也是夠慢的。下一個純粹的細粒度響應庫會在很後面的對比中才會出現。
接下來,我們將主要根據庫的打包大小來對比啟動指標類別。
圖片第 4 組啟動成績
這裡的排名和之前有很大區別。Alpine 的速度表現最差,但我們可以看到它的包最小,啟動時間最短。Marko(來自 eBay)緊隨其後,接下來是 Rax(來自阿里巴巴)。這三個庫都是為輕量級客戶端交互的服務端渲染而構建的。所以它們的性能只能排在第 4 組,但啟動成績卻在這一組中領先。
表格的後半部分是我們在基準測試中包體積最大的幾名選手,其中 Ember 成績最差,大小是其他任何實現的兩倍以上。我不知道為什麼要花費超過半兆字節才能渲染出這個表,但不管怎樣這都會拖累啟動性能。
最後一個類別是內存消耗。
圖片第 4 組內存成績
內存成績往往是和之前兩類成績相關聯的,因為它會對性能產生重大影響,同時大型庫往往會使用更多內存。Alpine、Marko 和 React 位居前三。老舊的細粒度響應庫使用的內存最多,Ember 敬陪末座。Ember 太吃內存了。僅在頁面上渲染 6 個按鈕之後,它用的內存已經比標準參考在整個套件中所用的還要多了。
總體來看,這一組庫在 GitHub 上總共有 30 萬星,可能在 NPM 下載中佔據了大部分份額。在這組選手中,Marko 和 Alpine 的平均排名最高。React 排名第三,而速度表現是最好的。
本組中的一些框架在市場份額上遙遙領先,而一些老舊的響應庫已經是昨日黃花。接下來我們研究一些表現更好的選手。
這一組裡的框架可以說在設計時考慮到了性能需求。它們在包大小指標上也下了功夫,並且在創建和更新成本之間找到了平衡。
我們在本組中能看到五花八門的方法。比如 Yew,一個 WebAssembly 框架(用 Rust 編寫);LitElement,一個 Web 組件。最近發布的 Vue 3 是這個框架的重大進化,讓它走出了第 4 組,開始和一些之前沒有遇到過的對手直接競爭。
閒話少提,下面來看看它們的表現。
圖片第 3 組速度成績
整體分數提高了一些,同組內的差距也大多了。Preact 是本組中速度最快的,LitElement 則以微弱劣勢緊隨其後。Vue 3 和 Riot 速度相近,都位居中遊,它們也都有過響應性和 VDOM 並存的歷史。Mithril 是最早將速度放在首位的 VDOM 庫之一,而 Yew 作為唯一的 WASM 庫落在了最後。
在性能 profile 方面,所有這些庫都差不多。這組裡面沒有純粹的的響應庫。它們都使用自上向下的渲染方式,無論是 VDOM 還是簡單的 Tag Template Literal diff 都一樣。與上一組相比,它們的列表處理更智能些(參閱行交換性能)。但多數框架的行選擇性能還是最低一檔。
Yew 是個例外,但它的其它指標都差不少。我們看看其他測試有沒有什麼不同。
圖片第 3 組啟動成績
情況有所改觀,但在啟動指標方面 Preact 仍處於領先地位。Yew 是本組中唯一稱得上大型庫的。WASM 庫的確偏大。
這裡我們又能看到一些相近的成績。Vue 也很大,僅次於 Yew。Preact 和 Riot 的表現非常接近。Mithril 和 LitElement 也差不多,都位居中遊。
Preact 是 React 的一個 4kb 替代品,它顯然是我們目前見過的最小的庫,但後面還有更小的呢。不管怎樣,本組中的這些庫都不需要用戶太操心它們的包大小。
圖片第 3 組內存成績
Yew 這次贏了。在測試過的所有框架中,它的內存佔用量最少。WASM 庫在這方面往往能做得很好。其他成績都非常接近。Mithril 和 Preact 是最差的,但落後也不是很多。
這裡沒有什麼特別值得一提的東西。你可能會認為 LitElement 可能比其他庫(Yew 除外)更輕巧,因為它沒有像其他庫一樣使用虛擬 DOM。但我們稍後會看到,VDOM 並不意味著就要佔用更多內存。
Riot 和 Preact 的平均排名最高,其次是 LitElement,排名第三。Riot 雖然沒有明顯勝出,但在這一組中沒有弱點,因此取得了勝利。但是,這些框架中無論選了哪一個都很難會失望。至於 WASM 和 Web 組件,它們代表了許多人心目中 Web 的未來。
但我們還沒有完成。下一組代表了對 Web 未來的另一種思考。我們要進入編譯器的領域了。
這一組的競爭很激烈。本組中的多數庫都被稱為編譯語言,每種都有自己的風味。這組裡有不可變的結構化 Elm、受 Ruby 啟發的 Imba 和「正在消失的」Svelte。
注意:我發現,並不是所有人都熟悉 Svelte 以前那個「正在消失的框架」的綽號。這個綽號指的是它從輸出中基本編譯出自身的能力。我並不是說 Svelte 沒希望了,如果造成困擾我很抱歉。
比較特殊的是 HyperApp,它與同組其他選手完全相反。它沒有編譯器、沒有模板。只有 h 函數和一個最精簡的 Virtual DOM。
猜猜結果如何?
圖片第二組速度成績
勝出的竟然是最精簡的虛擬 DOM。與最近的流行觀念相反,事實證明虛擬 DOM 並不是糟糕性能的代名詞,而且編譯並沒有給其他庫帶來顯著優勢。
在編譯的庫中,我們實際可以看到 3 種不同的渲染方法,平均速度都差不多。Imba 使用 DOM 一致性對比方法(和我們之前看到的 LitElement 很像),Elm 使用虛擬 DOM,排在最後的 Svelte 使用了一個組件響應系統。
你應該注意的是,虛擬 DOM 庫的行選擇性能最差,體現出了它們的額外開銷。但這些庫還有著更快的初始渲染速度。如果你仔細觀察到目前為止的結果,應該能注意到虛擬 DOM 庫與響應庫之間共有的這一特性。不過在其他指標上大家的速度都差不多。
所以我們繼續分析。這些編譯器的啟動時間 / 包大小怎麼樣呢?
圖片第 2 組啟動成績
如你所見,這個小巧的虛擬 DOM 庫不僅速度更快,包也比其他庫更小。實際上,HyperApp 是我們所有庫中最小的實現。編譯器在包大小方面沒法取勝。
它和 Svelte 都比我們的標準 JavaScript 參考構建更小。為什麼會這樣呢?因為它們的抽象是以一種更加可重用的方式編寫的,所以用到的代碼更少。標準 JS 實現的優化主要針對性能而非包大小。
Elm 的包大小在本組中表現也不錯。但是,Imba 的成績開始落到了第 4 組的水平上。
剩下的就是內存佔用了,也是編譯器大放異彩的最後機會。
圖片第 2 組內存成績
內存結果非常接近,幾乎是平局,但是 Svelte 終於為編譯器贏得了勝利。這是對虛擬 DOM 的一場成功復仇,雖然前者速度更快,體積更小。
老實說,所有這些庫都有出色的內存 profile。現在我們應該很清楚地看到更快的速度與更低的內存佔用之間的關聯了。
不要相信宣傳口號?
對。很多事情比表面上看起來複雜很多。精心設計的系統,無論是運行時還是編譯時,或者無論採用何種技術方法,都可以獲得高性能的表現。
HyperApp 是本組的大贏家,緊隨其後的是 Svelte,然後是 Elm 和 Imba。它們都對性能非常重視,所以你可以期望這些庫在大多數情況下都可以提供最頂尖的性能表現。
後面還有什麼結論呢?
我可以告訴你,聲明式 JavaScript 庫的性能也可以非常出色,不管是純粹的 WASM、Web Worker 或隨便什麼技術它都不會怕的。於是我們來到了……
某種程度上本組可以被稱為「快如閃電」,我想這些庫也用過這個口號。其實你留心的話,會意識到剩下的選手只有兩位了。實際上,這個層次上有一些庫在不斷創造新的紀錄,但其中只有兩個庫比較流行。它們比手工優化的純 JS 平均要慢 20%。
第 1 組速度成績
結果值得研究一番。這裡我們有兩個庫,查看它們的代碼會發現,雖然它們的速度相近,但使用的方法完全不同。Inferno 是業內性能最高的虛擬 DOM 庫之一。也就是說在速度最快的 5 名選手中有 3 個是虛擬 DOM 庫。行選擇測試的速度下降可以視為證據。
另一方面,Solid 使用了細粒度的響應性,和第 4 組中最慢的幾個老庫類似。這種技術又能佔據榜首是挺奇怪的事情,但正如我們所見,Solid 解決了它們的缺陷。它的創建時間與更新時間一樣快。與純 JavaScript 只有 5%差距,這一事實令人難以置信。
奇怪的是,Inferno 和 Solid 的共同點是 JSX 模板和受 React 啟發的 API。在其他那些有著定製優化 DSL 的庫中,你大概不會看到這種東西和頂級性能同時出現。但正如 HyperApp 展示的一樣,某些事情對性能的影響比人們想像的要小很多。
第 1 組啟動成績
繼 HyperApp 和 Svelte 後,Solid 是第三個比純 JS 實現更小的庫,但 Inferno 也不落下風。
雖然高性能庫一般會比較小,但有時添加更多代碼可以提高性能,帶來更好的列表一致性對比算法、更明確的防護措施、更精細的更新,等等。
Inferno 可能比前幾組中的某些庫更大,但它也還是一個不到 10kb 的庫,而性能則超過幾乎所有對手。
第 1 組內存成績
如你所見,除了使用 WASM 的 Yew 以外,它們是整個對比中內存消耗最少的框架。考慮到它們的速度表現,這個結果並不奇怪。
這些內存消耗數字反映了設計者對對象和閉包創建有著非常深度的思考。兩個庫都做了定製的 JSX 轉換,帶來了很多收益。
內存性能的提升對 Solid 尤為重要,因為 Solid 與大多數細粒度的響應庫一樣,都將 CPU 開銷換成了內存消耗。在這種對比中,Solid 之所以採用了和多數最慢的庫類似的技術,卻能提供最出色的成績,很大一部分功勞都來自於它成功解決了內存消耗問題。
接近極限。
雖說純 JavaScript 是上限,但我們這裡的聲明式庫性能幾乎沒怎麼落後,你完全感覺不到它們的差距。雖然我們都覺得 DOM 不行,但只要精心設計,有很多技術都可以高效渲染 DOM。
這裡我們也看到了證據。Solid 在十年前就被認為是古老而緩慢的技術,可它竟然奪得了性能冠軍,而 Inferno 再一次證明了虛擬 DOM 可以高效完成任何任務。
在構建 JavaScript 前端時,我們有很多選擇。本文只是幫助大家快速了解框架帶來的性能開銷。當涉及到應用程式中的實際性能主題時,用戶代碼的影響會更大。
但我想在這裡真正強調的是,測試你的解決方案並了解性能表現是很重要的。現實和宣傳總是會有差異。虛擬 DOM 不見得就那麼慢。我們不能保證編譯器一定會帶來最小的包。定製模板 DSL 不見得最佳選項。
最後我把所有庫放在一起做個對比。某個庫排在後面,並不一定意味著它就很慢,但是與這些表現出色的競爭對手相比它的得分更差一些。
所有框架放在同一張表上。
速度
啟動
內存
最終排名
所有結果都加到一個列表中(第 1 名得 20 分,最後 1 名得 1 分)。在平局的情況下速度成績優先。
Solid(57)
HyperApp(54)
Inferno(51)
Svelte(51)
Elm(46)
Riot(40)
Preact(39)
Imba(36)
lit-html(36)
Yew(32)
Vue(29)
Mithril(29)
Marko(28)
Alpine(28)
React(19)
Rax(16)
Angular(12)
Knockout(11)
Ractive(8)
Ember(6)
特別感謝 AJ Meyghani 在 2018 年寫的這篇對比文章,這篇文章正是受其啟發:
https://medium.com/@ajmeyghani/javascript-frameworks-performance-comparison-c566d19ab65b
延伸閱讀
https://medium.com/javascript-in-plain-english/javascript-frameworks-performance-comparison-2020-cd881ac21fce
目前在某大廠的小部門做技術負責人,歡迎找我交流技術。在投資方面也頗有心得,人生第一套學區房靠自己努力獲得的!
歡迎加好友,互相圍觀朋友圈,做個點讚之交~