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事件(click
,submit
,等)。开发人员常常编写使用el.addEventListener(...)
或使用诸如jQuery的库来添加DOM事件监听器的代码。当您使用Marko构建UI组件时,您仍然可以执行此操作,但是在初始化大量组件时,在附加侦听器时会出现开销。相反,Marko建议使用声明式事件绑定,如下所示:
<button type="button" on-click("handleClick")>
Click Me
</button>
当使用声明性事件绑定时,事实上没有附加任何DOM事件侦听器。相反,Marko会为每个DOM事件(在启动时完成)为页面的根DOM元素附加一个监听器。当Marko在根节点处收到事件时,它将把事件委托给该事件的相应组件。这是通过查看event.target
属性来查看事件发生的位置,然后向上遍历树来查找需要通知的组件。因此,当根目录捕获DOM事件时,会做更多的工作,但这种方法使用的内存少得多,并减少了初始化时需要完成的。将事件委派给组件的额外开销并不明显,因此这是一个非常有益的优化。