字符串(String)模板引擎被视为是有害的


发布者 andrewleeson  发布时间 1396179994244
关键字 编程技巧  Html5 

如果你已经开始编程了一段时间的话,那么你应该读过自己该读的“Considered harmful(被认为是有害的)”的文章,因此你已经知道标题下跟的通常是一连串有偏见的言论和精挑细选的证据。

我不能保证这将会有任何的不同。但我们要覆盖的概念我觉得是前端社区应该被谈论。

大概内容是:用字符串模板生成HTML是一个低效的过程,它在我们如何概念化Web应用上强加一些不必要的限制因素。如果你用像Ractive.js这样的库或MeteorEmber这样的测试版frameworks,那么你可以没有缺陷的利用模板。如果你不需要使用模板的话,那么React也是一个非常不错的选择。

模板101

有大量JavaScript开发人员可用的模板库——通过Garann Means的“模板引擎的选择”来获取一些例子。其中绝大多数都是字符串模板引擎,也就是说,他们用字符串模板和一些数据作为输入(通常类似于HTML,除了占位符标签),输出另一个字符串:

var template = '<h1>Hello {{name}}!</h1>';
var data = { name: 'world' };

Mustache.render( template, data );
// -> '<h1>Hello world!</h1>'

上面的例子中,我们使用了mustache.js——优秀的mustache模板语言的一个实现,很多地方都能找到它的变体。它很受欢迎这可无非议——你在几秒钟之内就能学会,而且它很吸引人(我认为),可读性好、简洁。形式规范的存在使我们能够选择实现方法,比如Hogan.js在代码长度和性能方面的竞争。

另外,开发人员喜欢模板是有充分理由的。并非所有的模板语言在实践中都被Pete Hunt视为是有害的,核心队伍以各种准备充分的形式反对着模板——但有足够多的人不会在短时间内离开。

那么为什么字符串(String)模板效率低下呢?

用模板和一些数据来生成HTML不是一项特别繁重的任务。那些你必须要为HTML做的事才是效率低下的部分。

假如你有一个代表改变数据的UI:

<div class='score'>
    <p>Current score: <strong>{{currentScore}}</strong></p>
    <p>High score: <strong>{{highScore}}</strong></p>
</div>

在我们假设的射击游戏中,currentScore 属性可能会经常变化。如果我们玩得很好,那highScore 属性可能会发生变化,但在许多游戏它的值不会被改变。然而,当我们重新刷新模板且在代码中插入像score.innerHTML = rendered$score.html(rendered)这样的东西,无论如何,每次分数改变时目前的DOM将被丢弃。在这个例子中,有一个<div>,两个<p>元素,两个<strong>,四个文本节点和一个属性节点(class节点)。 

这是一个相当简单的例子。当模板变得更大时,当每次重新刷新时需要摧毁的东西就会变得越多——这意味着垃圾收集器需要做更多的工作。这个过程很慢且浪费时间,当你需要在这些节点上监听DOM事件时,我们甚至还没有谈论到底发生了什么。通过插入新加载的HTML到DOM中来更新视图就像当你需要做的仅仅是清理下你的窗户时你的房子却被拆除重建了。

出示证据

可能你已经知道innerHTML比DOM操作要快(在老版本的IE浏览器中确实如此,不过在现代浏览器中DOM操作要更快一些),或者我们谈论的不是一个很大的区别之处。让我们来看看一些数字。

不久前,Jeremy Ashkenas——Backbone.js的创建者和全能的JavaScript英雄,创建了一个JSFiddle 标准展示了Backbone的数据绑定性能明显优于Ember。

这个标准是有不同分支的,包括React——它能很轻松的打败其它两个竞争者(backbone和ember)。后来,https://twitter.com/ebryn更新了这个标准,让其包含一个它致力研究的Ember开发版本,这让它轻松的击败了React。

你不应该过分解读这些标准——这只是测试React而已,比如说它适不适合。其它的标准将会有不同的故事。但有一个标准我们应该发现它很有趣——backbone VS backbone。

打开这个JSFiddle。这是以各种分支为内容的Jeremy的原创(我不怀疑还有别的)。试试各种实现方法来看看各种方法之间级的区别。Backbone(更快)的例子与backbone的例子是一样的,除了不用render()方法而用字符串模板来替代,它有一个用DOM操作的update()方法。这比频繁插入大数据的HTML的而言可快多了。

它是一个稻草人——我可不想把我的视图写成那样

开发人员直观地认识到这个问题。我们不写覆盖整个应用程序的大模板——相反的,我们写模板来让一部分内容变得更为精简,然后再将其整合进代码中。我们不为一个todo列表项写模板,我们为单个todo项写模板,且为每个模板创建一个视图对象。

这正是问题所在。我们最终考虑用户界面的时候侧重的是原子性而不是全面性——我们应用程序中最需要考虑清楚的地方就是将其分成许多不同的文件。可能我们已经远离‘jQuery spaghetti’了,但在很多情况下它已经被MVC胶带给替代了。

可能你觉得写小规模,高度集中的模板是一件好事——有时候你这想法是正确的。关键问题是这些架构决策不应该在我们缺少工作所需的工具和方法时强加于我们。

 DOM模板怎么样?

对于字符串模板来说还是有一些选择的。DOM模板系统,例如在KnockoutAngular中,将许多的数据绑定放到元素属性中——比如Knockout的data-bind或Angular的ng-repeat。这些系统将避免不断处理DOM垃圾的问题。

但这是要付出代价的:

  • 由于数据绑定指令都是从DOM读取的,模板必须是可渲染的HTML,限制模板语言的表现力。语法方面字符串模板语言通常比DOM模板有更好的可读性。
  • 你可能需要做一些工作来防止FOUC——Flash Of Unbound Content(浏览器无样式内容闪烁)——可以用ng-cloak补丁来防范。
  • 如果没有包含JSDOM或PhantomJS的鲁贝戈德堡式系统,那么服务器端的渲染是不可能实现的。
  • 通过研究DOM,你会经常发现“数据绑定的繁琐”——很多像ng-model这样的无效属性

那么答案是什么呢?

我们会有蛋糕吃的。我们可以摒弃之前讨论的所有缺点,用声明的方便性和表现的强大性这些优点来使用字符串模板。怎么实现呢?通过使用一个能在HTML和模板语言之间进行转换的模板解析器可以完成。

Reactive.js,一个原先在theguardian.com网站上发展起来的,作为这个方法的先驱的UI库。Reactive使用的是mustache的向后兼容变体(在未来可以支持其它语言),但不是生成字符串,它将生成的的内容解析成一个抽象的语法树

这棵抽象语法树随后会与数据(通常是原生JavaScript对象或POJO)结合来构造一个轻量级数据绑定的能在浏览器或服务器上运行的“平行DOM”。通过它,在如何以一种保守的方式更新真正的DOM这个问题上有可能做出明智的决定。

由于它只与需求部分的DOM接触,你可以适用你程序的方式来写模板,不必担心频繁清理垃圾而导致的性能问题,也不必担心连接大量的视图对象。

我不会深入研究项目的细节,除了注意到我们也开创了“标记驱动数据可视化”的概念,以前这是不可能的,因为没有可以替代innerHTML的SVG。你可以在这学习60秒钟

但这是一个将越来越流利的方法。Meteor——全栈的JavaScript框架,已经开发了一个叫Blaze的库,其使用了类似的技术,将字符串模板解析成领域专用语言(DSL)。同时,Ember不久将宣布HTMLBars(可能还是最终的名称)可用的消息了,一个HTML-aware版本的Handlebars。远离成为激烈性能(drastic performance)提升主要责任对象的可能,这意味着Ember的用户现在已经有一个好得多的语法可用了。

将来

我相信这个技术的各种版本将会代表着在WEB上创建用户界面的将来。这被认为是一个健壮、高性能且无限灵活的方法,且不会为了让生产效率更高要求开发人员学习一些陌生的新概念。

如果你想成为这个未来技术的一份子且帮忙创建它,来GitHub加入(全新的)我们的邮件列表吧。你可以在推特上关注@RactiveJS