Marko和react,preact,vue相比快在哪里

Marko和react,preact,vue相比快在哪里

在eBay,我们正在使用[Marko](http://markojs.com/)每天处理超过十亿的请求,这就要求我们精简我们的开源UI库Marko。我们大大优化了Marko ,以实现快速渲染,高级性能技术,并实现了最小的页面重量(〜10kb gzipped)。性能只是一个方面,因为我们还必须扩展Marko以支持数百个团队的开发,从而允许开发人员有效地创建可维护和强大的Web应用程序。

我们已经创建了我们自己的benchMarks进行比较,也已经将Marko添加到其他基准测试中,但是基准测试并不总是值得信赖的。尽管我们尽可能公平地对待我们的基准,但最重要的是在现实世界的应用中的表现,而不是专注于微观基准。这就是V8团队转而采用新的方法来衡量和理解现实JavaScript性能的一个原因。

同样,我们已经观察过我们的开发人员实际开发中如何编写他们的Marko组件,并且发现了可以进一步优化的模式。而不是关注本文中的基准测试,我想关注我们应用于Marko的优化细节。

多个编译输出

Marko是一个在服务器和浏览器中运行的同构UI库。正如Michael Rawlings在“ 服务器端渲染染 ”中提到的,当在服务器上呈现时,Marko直接呈现可以作为HTTP响应发送的文档(HTML)的字符串表示形式。

在浏览器中呈现时,必须解析HTML字符串才能更新DOM。因此,Marko通过程序将视图编译为直接呈现到虚拟文档(VDOM)树,这可以针对浏览器有效地更新真实的DOM。

给出以下模板:

<div>Hello ${input.name}!</div>

编译为服务器

编译输出针对服务器上的HTML输出进行了优化:

var marko_template = require("marko/html").t(__filename),
    marko_helpers = require("marko/runtime/html/helpers"),
    marko_escapeXml = marko_helpers.x;

function render(input, out) {
  out.w("
Hello " + marko_escapeXml(input.name) + "!
"
); }

编译为浏览器

编译输出针对浏览器中的虚拟DOM渲染进行了优化:

var marko_template = require("marko/vdom").t(__filename);

function render(input, out) {
  out.e("DIV", null, 3)
    .t("Hello ")
    .t(input.name)
    .t("!");
}

模块化运行时

Marko运行时并不作为单个JavaScript文件分发。相反,Marko编译器会生成一个JavaScript模块,该模块仅导入实际需要的运行时部分。这允许我们向Marko添加新功能,而不会使现有应用程序膨胀。例如,给出以下模板:

$ var color = 'red';
<div style={backgroundColor: color}></div>

在上面的示例中,需要额外的运行时代码来根据所提供style的JavaScript对象呈现属性。导入styleAttr助手的编译代码如下所示:

var marko_styleAttr = require("marko/runtime/vdom/helper-styleAttr");

function render(input, out) {
  var color = 'red';
  out.e("DIV", {
      style: marko_styleAttr({
          backgroundColor: color
        })
    }, 0, 4);
}

高性能的服务器端渲染

与基于专门进行虚拟DOM渲染的JSX的解决方案相比,Marko在服务器端渲染方面具有巨大的优势。当渲染到服务器上的虚拟DOM树时,它是一个两步的过程来呈现HTML:

  • 首先在内存中生成一个完整的虚拟DOM树
  • 第二遍将虚拟DOM树序列化为可以通过线路发送的HTML字符串(这需要遍历整个树结构)

相比之下,Marko直接一次性渲染一整个HTML流。没有中间树数据结构。

静态子树的编译时优化

给出以下模板:

<div>This is a <strong>static</strong> node</div>

Marko将会认识到,模板片段每次都会产生相同的输出,因此会像以下编译输出一样创建一个虚拟DOM节点:

var marko_node0 = marko_createElement("DIV", null, 3, ...)
  .t("This is a ")
  .e("STRONG", null, 1)
    .t("static")
  .t(" node");

function render(input, out) {
  out.n(marko_node0);
}

渲染静态子树几乎是零成本。此外,Marko将跳过对比/修补静态子树。

同样,在服务器上,Marko会将模板的静态部分合并成一个字符串:

function render(input, out) {
  out.w("
This is a static node
"
); }

静态属性的编译时优化

Marko还将优化动态元素的静态属性。

给出以下模板:

<div.hello>Hello ${input.name}!</div>

Marko将产生以下编译输出:

var marko_attrs0 = {
        "class": "hello"
      };

function render(input, out) {
  out.e("DIV", marko_attrs0, 3)
    .t("Hello ")
    .t(input.name)
    .t("!");
}

请注意,属性对象只创建一次,它用于每个渲染。另外,静态属性不会发生 diffing/patching

智能编译器

使用Marko,我们倾向于在编译时尽可能多地执行。这使我们的编译器更加复杂,但它在运行时给我们带来了很大的收获。我们有〜90%的代码覆盖率和超过2000个测试,以确保编译器正常工作。此外,在许多情况下,Marko编译器为给定的模板提供运行时提示,以便运行时可以针对特定模式进行优化。例如,识别的Marko如果HTML元素仅具有class/id/style定义和做版本比较时/修补运行时优化了这些虚拟DOM节点(Marko编译器生成的代码,标记简单的虚拟DOM节点用于针对 diffing/patching 逻辑)。

事件委托

如果你正在建立一个UI组件,您将很可能需要编写代码来处理不同的DOM事件(clicksubmit,等)。开发人员常常编写使用el.addEventListener(...)或使用诸如jQuery的库来添加DOM事件监听器的代码。当您使用Marko构建UI组件时,您仍然可以执行此操作,但是在初始化大量组件时,在附加侦听器时会出现开销。相反,Marko建议使用声明式事件绑定,如下所示:

<button type="button" on-click("handleClick")>
  Click Me
</button>

当使用声明性事件绑定时,事实上没有附加任何DOM事件侦听器。相反,Marko会为每个DOM事件(在启动时完成)为页面的根DOM元素附加一个监听器。当Marko在根节点处收到事件时,它将把事件委托给该事件的相应组件。这是通过查看event.target属性来查看事件发生的位置,然后向上遍历树来查找需要通知的组件。因此,当根目录捕获DOM事件时,会做更多的工作,但这种方法使用的内存少得多,并减少了初始化时需要完成的。将事件委派给组件的额外开销并不明显,因此这是一个非常有益的优化。

译文:why-is-marko-fast

0%