我終于成功登上了JS 框架榜單,并且僅落后于 React 4 名!
前言
如期而至,我獨立開發的 JavaScript 框架 Strve.js 迎來了一個大版本5.6.2。此次版本距離上次大版本發布已經接近半年之多,為什么這么長時間沒有發布新的大版本呢?主要是研究 Strve.js 如何支持單文件組件,使代碼智能提示、代碼格式化方面更加友好。之前也發布了 Strve SFC,但是由于其語法規則的繁瑣以及是在運行時編譯的種種原因,我果斷放棄了這個方案的繼續研究。而這次的版本5.6.2成功解決了代碼智能提示、代碼格式化方面友好的問題,另外還增加了很多錦上添花的特性,這些都歸功于我們這次版本成功支持JSX語法。熟悉React的朋友知道,JSX語法非常靈活。 而 Strve.js 一大特性也就是靈活操作代碼塊,這里的代碼塊我們可以理解成函數,而JSX語法在一定場景下也恰恰滿足了我們這種需求。
那么,我們如何在 Strve 項目中使用JSX語法呢?我們在Strve項目構建工具 CreateStrveApp 預置了模版,你可以選擇 strve-jsx 或者 strve-jsx-apps 模版即可。我們使用 CreateStrveApp 搭建完 Strve 項目會發現,同時安裝了babelPluginStrve、babelPluginJsxToStrve,這是因為我們需要使用 babelPluginJsxToStrve 將 JSX 轉換為標簽模版,之后再使用babelPluginStrve 將標簽模版轉換為 Virtual DOM,進而實現差異化更新視圖。
嘗試
我既然發布出了一個大版本,并且個人還算比較滿意。那么下一步我如何推廣它呢?畢竟毛遂自薦有時候還是非常有意義的。所以,我打算通過js-framework-benchmark 這個項目評估下性能。
js-framework-benchmark 是什么?我們這里就簡單介紹下 js-framework-benchmark,它是一個用于比較 JavaScript 框架性能的項目。它旨在通過執行一系列基準測試來評估不同框架在各種場景下的性能表現。這些基準測試包括渲染大量數據、更新數據、處理復雜的 UI 組件等。通過運行這些基準測試,可以比較不同框架在各種方面的性能優劣,并幫助開發人員選擇最適合其需求的框架。js-framework-benchmark 項目提供了一個包含多個流行 JavaScript 框架的基準測試套件。這些框架包括 Angular、React、Vue.js、Ember.js 等。每個框架都會在相同的測試場景下運行,然后記錄下執行時間和內存使用情況等性能指標。通過比較這些指標,可以得出不同框架的性能差異。這個項目的目標是幫助開發人員了解不同 JavaScript 框架的性能特點,以便在選擇框架時能夠做出更加明智的決策。同時,它也可以促進框架開發者之間的競爭,推動框架的不斷改進和優化。
那么,我們就抱著試試的心態去運行下這個項目。
測試
我們進入js-framework-benchmark Github主頁,然后 clone 下這個項目。
git clone https://github.com/krausest/js-framework-benchmark.git
然后,我們 clone 到本地之后,打開 README.md 文件找到如何評估框架。大體瀏覽之后,我們得出的結論是:通過使用自己的框架完成js-framework-benchmark規定的練習項目。
那么,我們就照著其他框架已經開發完成的示例進行開發吧!在開發之前,我們必須要了解js-framework-benchmark 中有兩種模式。一種是keyed,另一種是non-keyed。在 js-framework-benchmark 中,"keyed" 模式是指通過給數據項分配一個唯一標識符作為 "key" 屬性,從而實現數據項與 DOM 節點之間的一對一關系。當數據發生變化時,與之相關聯的 DOM 節點也會相應更新。而 "non-keyed" 模式是指當數據項發生變化時,可能會修改之前與其他數據項關聯的 DOM 節點。因為 Strve 暫時沒有類似唯一標識符這種特性,所以我們選擇non-keyed模式。
我們打開項目下/frameworks/non-keyed文件夾,找一個案例框架看一下它們開發的項目,我們選擇 Vue 吧! 我們根據它開發的樣例遷移到自己的框架中去。為了測試新版本,我們將使用JSX語法進行開發。
import { createApp, setData } from "strve-js";
import { buildData } from "./data.js";
let selected = undefined;
let rows = [];
function setRows(update = rows.slice()) {
setData(
() => {
rows = update;
},
{
name: TbodyComponent,
}
);
}
function add() {
const data = rows.concat(buildData(1000));
setData(
() => {
rows = data;
},
{
name: TbodyComponent,
}
);
}
function remove(id) {
rows.splice(
rows.findIndex((d) => d.id === id),
1
);
setRows();
}
function select(id) {
setData(
() => {
selected = +id;
},
{
name: TbodyComponent,
}
);
}
function run() {
setRows(buildData());
selected = undefined;
}
function update() {
for (let i = 0; i < rows.length; i += 10) {
rows[i].label += " !!!";
}
setRows();
}
function runLots() {
setRows(buildData(10000));
selected = undefined;
}
function clear() {
setRows([]);
selected = undefined;
}
function swapRows() {
if (rows.length > 998) {
const d1 = rows[1];
const d998 = rows[998];
rows[1] = d998;
rows[998] = d1;
setRows();
}
}
function TbodyComponent() {
return (
<tbody $key>
{rows.map((item) => (
<tr
class={item.id === selected ? "danger" : ""}
data-label={item.label}
$key
>
<td class="col-md-1" $key>
{item.id}
</td>
<td class="col-md-4">
<a onClick={() => select(item.id)} $key>
{item.label}
</a>
</td>
<td class="col-md-1">
<a onClick={() => remove(item.id)} $key>
<span
class="glyphicon glyphicon-remove"
aria-hidden="true"
></span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
))}
</tbody>
);
}
function MainBody() {
return (
<>
<div class="jumbotron">
<div class="row">
<div class="col-md-6">
<h1>Strve-non-keyed</h1>
</div>
<div class="col-md-6">
<div class="row">
<div class="col-sm-6 smallpad">
<button
type="button"
class="btn btn-primary btn-block"
id="run"
onClick={run}
>
Create 1,000 rows
</button>
</div>
<div class="col-sm-6 smallpad">
<button
type="button"
class="btn btn-primary btn-block"
id="runlots"
onClick={runLots}
>
Create 10,000 rows
</button>
</div>
<div class="col-sm-6 smallpad">
<button
type="button"
class="btn btn-primary btn-block"
id="add"
onClick={add}
>
Append 1,000 rows
</button>
</div>
<div class="col-sm-6 smallpad">
<button
type="button"
class="btn btn-primary btn-block"
id="update"
onClick={update}
>
Update every 10th row
</button>
</div>
<div class="col-sm-6 smallpad">
<button
type="button"
class="btn btn-primary btn-block"
id="clear"
onClick={clear}
>
Clear
</button>
</div>
<div class="col-sm-6 smallpad">
<button
type="button"
class="btn btn-primary btn-block"
id="swaprows"
onClick={swapRows}
>
Swap Rows
</button>
</div>
</div>
</div>
</div>
</div>
<table class="table table-hover table-striped test-data">
<component $name={TbodyComponent.name}>{TbodyComponent()}</component>
</table>
<span
class="preloadicon glyphicon glyphicon-remove"
aria-hidden="true"
></span>
</>
);
}
createApp(() => MainBody()).mount("#main");
其實,我們雖然使用了JSX語法,但是你會發現有很多特性并不與JSX語法真正相同,比如我們可以直接使用 class 去表示樣式類名屬性,而不能使用 className 表示。
評估案例項目開發完成了,我們下一步就要測試一下項目是否符合評估標準。
npm run bench non-keyed/strve
測試標準包括:
- create rows:創建行,頁面加載后創建 1000 行的持續時間(無預熱)
- replace all rows:替換所有行,替換表中所有 1000 行所需的時間(5 次預熱循環)。該指標最大的價值就是了解當頁面上的大部分內容發生變化時庫的執行方式。
- partial update:部分更新,對于具有 10000 行的表,每 10 行更新一次文本(進行 5 次預熱循環)。該指標是動畫性能和深層嵌套數據結構開銷等方面的最佳指標。
- select row:選擇行,在單擊行時高亮顯示該行所需的時間(進行 5 次預熱循環)。
- swap rows:交換行,在包含 1000 行的表中交換 2 行的時間(進行 5 次預熱迭代)。
- remove row:刪除行,在包含 1,000 行的表格上移除一行所需的時間(有 5 次預熱迭代),該指標可能變化最少,因為它比庫的任何開銷更多地測試瀏覽器布局變化(因為所有行向上移動)。
- create many rows:創建多行,創建 10000 行所需的時間(沒有預熱),該指標更容易受到內存開銷的影響,并且對于效率較低的庫來說,擴展性會更差。
- append rows to large table:追加行到大型表格,在包含 10000 行的表格上添加 1000 行所需的時間(沒有預熱)。
- clear rows:清空行,清空包含 10000 行的表格所需的時間(沒有預熱),該指標說明了庫清理代碼的成本,內存使用對這個指標的影響很大,因為瀏覽器需要更多的 GC。
最終,Strve 頂住了壓力,通過了測試。
看到了successful run之后,覺得特別開心!那種成就感是任何事物都難以代替的。
跑分
我們既然通過了測試,那么下一步我們將與前端兩大框架Vue、React進行比較跑分,我們先在我自己本地環境上跑一下,看一下效果。
性能測試基準分為三類:
- 持續時間
- 啟動指標
- 內存分配
持續時間
啟動指標
內存分配
圖片
總體而言,我感覺還不錯,畢竟跟兩個大哥在比較。到這里我還是覺得不夠,跟其他框架比比呢!
提交
只要框架通過了測試,并且按照提交PR的規定提交,是可以被選錄到 js-framework-benchmark 中去的。
好,那我們就去試試!
又一個比較有成就感的事!提交的PR被作者合并了!
成績單
我迫不及待的去榜單上看下我的排名,會不會墊底啊!
因為瀏覽器版本發布的時差問題,暫時 Official results ( 官方結果 ) 還沒有發布最新結果,我們可以先來 Snapshot of the results ( 快照結果 ) 中查看。
我們打開下方網址就可以看到JS框架的最新榜單了。
https://krausest.github.io/js-framework-benchmark/current.html
我們在持續時間這個類別下從后往前找,目前63個框架我居然排名 50 名,并且大名鼎鼎的 React 排名45名。
我們先不激動,我們再看下啟動指標類別。Strve 平均分數是1.04,我看了看好幾個框架分數是1.04
。Strve 可以排到前8名。
我們再穩一下,繼續看內存分配這個類別。Strve 平均分數是1.40,Strve 可以排到前12名。
意義
js-framework-benchmark 的測試結果是相對準確的,因為它是針對同樣的測試樣本和基準測試情境進行比較,可以提供框架之間的相對性能比較。然而,需要注意的是,這個測試結果也只是反映了測試條件下的性能表現。框架實際的性能可能還會受到很多方面的影響。 此外,js-framework-benchmark 測試結果也不應該成為選擇框架的唯一指標。在選擇框架時,還需要考慮框架的生態、開發效率、易用性等多方面因素,而不僅僅是性能表現。
雖然,Strve 跟 React 比較是有點招黑,但是不妨這樣想,榜樣的力量是巨大的!只有站在巨人的肩膀上才能望得更遠!
Strve 要走的路還有很長,入選JS框架榜單使我更加明確了方向。我覺得做自己喜歡做得事情,這樣才會有意義!