JS框架性能對比:Soild 高居榜首,Vue、React 和 Angular 竟紛紛跌出前十

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