我终于成功登上了JS 框架榜单,并且仅落后于 React 4 名!

前端历劫之路

共 9254字,需浏览 19分钟

 ·

2023-07-19 12:27

前言

如期而至,我独立开发的 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 项目会发现,同时安装了babelPluginStrvebabelPluginJsxToStrve,这是因为我们需要使用 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规定的练习项目。

9f361ea67bfa29c2daf255e961ac22db.webp

那么,我们就照着其他框架已经开发完成的示例进行开发吧!在开发之前,我们必须要了解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


7d7621cdd065d59cf49f0ce0ab1529a6.webp

测试标准包括:

  • 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 顶住了压力,通过了测试。

5451594285411131a8b91dda7950800f.webp

看到了successful run之后,觉得特别开心!那种成就感是任何事物都难以代替的。

跑分

我们既然通过了测试,那么下一步我们将与前端两大框架Vue、React进行比较跑分,我们先在我自己本地环境上跑一下,看一下效果。

性能测试基准分为三类:

  • 持续时间
  • 启动指标
  • 内存分配

持续时间

3878fc4db98c8958a7db4c74c96bc748.webp

启动指标

613f5ed1f805a4f4f4db8db9193b8ca1.webp

内存分配

59a2fda6d0e27f4b2f335c4a518172c3.webp

总体而言,我感觉还不错,毕竟跟两个大哥在比较。到这里我还是觉得不够,跟其他框架比比呢!

提交

只要框架通过了测试,并且按照提交PR的规定提交,是可以被选录到 js-framework-benchmark 中去的。

好,那我们就去试试!

946a8913cceedab8b2f0a115d9ba41fc.webp

又一个比较有成就感的事!提交的PR被作者合并了!

成绩单

我迫不及待的去榜单上看下我的排名,会不会垫底啊!

因为浏览器版本发布的时差问题,暂时 Official results ( 官方结果 ) 还没有发布最新结果,我们可以先来 Snapshot of the results ( 快照结果 ) 中查看。

我们打开下方网址就可以看到JS框架的最新榜单了。

      
      https://krausest.github.io/js-framework-benchmark/current.html

我们在持续时间这个类别下从后往前找,目前63个框架我居然排名 50 名,并且大名鼎鼎的 React 排名45名。

8f3afe157bd8a5749af2d37518c2655e.webp

我们先不激动,我们再看下启动指标类别。Strve 平均分数是1.04,我看了看好几个框架分数是1.04。Strve 可以排到前8名。

92a8aa0b611666d7d348d8ad58e7ab35.webp

我们再稳一下,继续看内存分配这个类别。Strve 平均分数是1.40,Strve 可以排到前12名。

84c3d05b3fe1efb0993cd6b4b518b889.webp

意义

js-framework-benchmark 的测试结果是相对准确的,因为它是针对同样的测试样本和基准测试情境进行比较,可以提供框架之间的相对性能比较。然而,需要注意的是,这个测试结果也只是反映了测试条件下的性能表现。框架实际的性能可能还会受到很多方面的影响。 此外,js-framework-benchmark 测试结果也不应该成为选择框架的唯一指标。在选择框架时,还需要考虑框架的生态、开发效率、易用性等多方面因素,而不仅仅是性能表现。

虽然,Strve 跟 React 比较是有点招黑,但是不妨这样想,榜样的力量是巨大的!只有站在巨人的肩膀上才能望得更远!

Strve 要走的路还有很长,入选JS框架榜单使我更加明确了方向。我觉得做自己喜欢做得事情,这样才会有意义!

加油

Strve 要继续维护下去,我也会不断学习,继续精进。

Strve 源码仓库:https://github.com/maomincoding/strve

Strve 中文文档:https://maomincoding.gitee.io/strve-doc-zh/

谢谢大家的阅读!如果大家觉得Strve不错,麻烦帮我点下Star吧!






浏览 18
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报