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》"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章