此文已由作者郑海波授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验
String-based 和 Dom-based的模板技术都或多或少的依赖与innerHTML
, 它们的区别是一个是主要是为了Rendering 一个是为了 Parsing 提取信息
所以为什么不结合它们两者来完全移除对innerHTML
的依赖呢?
事实上,值得庆幸的是,已经有几个现实例子在这么做了。
例子
基本原理
就如图中所示,parse和compile的过程分别类似于String-based 模板技术 和 Dom-based模板技术。
下面来完整讲述下这两个过程
首先我们使用一个内建DSL来解析模板字符串并输出AST。
例如,在regularjs中,下面这段简单的模板字符串
<button {{#if !isLogin}} on-click={{this.login()}} {{/if}}>
{{isLogin? 'Login': 'Wellcome'}}
</button>'
会被解析为以下这段数据结构
[
{
"type": "element",
"tag": "button",
"attrs": [
{
"type": "if",
"test": {
"type": "expression",
"body": "(!_d_['isLogin'])",
"constant": false,
"setbody": false
},
"consequent": [
[
{
"type": "attribute",
"name": "on-click",
"value": {
"type": "expression",
"body": "_c_['login']()",
"constant": false,
"setbody": false
}
}
]
],
"alternate": []
}
],
"children": [
{
"type": "expression",
"body": "_d_['isLogin']?'Login':'Wellcome'",
"constant": false,
"setbody": false
}
]
}
]
这个过程有以下特点
dsl元素
与 xml元素
来实现最终视图层的活动性,即它们是dom-aware的,而在字符串模板中其实xml元素
完全可以无需关心,它们被统一视为文本元素
。结合特定的数据模型(在regularjs中,是一个裸数据), 模板引擎层级游历AST并递归生成Dom节点(不会涉及到innerHTML
). 与此同时,指令、事件和插值等binder也同时完成了绑定,使得最终产生的Dom是与Model相维系的,即是活动的.
事实上,Living template的compile过程相对与Dom-based的模板技术更加纯粹, 因为它完全的依照AST生成,而不是在原Dom上的改写。
以上面的模板代码的一个插值为例:{{isLogin? 'Login': 'Wellcome'}}
。一旦regularjs的引擎遇到这段模板与代表的语法元素节点,会进入如下函数处理
// some sourcecode from regularjs
walkers.expression = function(ast){
var node = document.createTextNode("");
this.$watch(ast, function(newval){
dom.text(node, "" + (newval == null? "": String(newval)));
})
return node;
}
正如我们所见, 归功于$watch
函数,一旦表达式发生改变,文本节点也会随之改变,这一切其实与angularjs并无两样(事实上regularjs同样也是基于脏检查)
与Dom-based 模板技术利用Dom节点承载信息所不同的是,它的中间产物AST 承载了所有Compile过程中需要的信息(语句, 指令, 属性...等等). 这带来几个好处
innerHTML
帮我们生成初始Dom如果你查看Living Template的输出,你会发现是这样的
只有需要的内容被输出了
总结Living templating
我们可以发现Living templating几乎同时拥有String-based和Dom-based模板技术的优点
利用一个如字符串模板的自定义DSL来描述结构来达到了语法上的灵活性,并在Parse后承载信息(AST)。而在Compile阶段,利用AST和Dom API
来完成View的组装,在组装过程中,我们同样可以引入Dom-based模板技术的诸如Directive
等优良的种子。
React当然也可以称之为一种模板解决方案,它同样也巧妙规避了innerHTML
,不过却使用的是截然不同的策略:react使用一种virtual dom
的技术,它也同样基于脏检查,不过与众不同的是,它的脏检查发生在view层面,即发生在virtual dom上,从而可以以较小的开销来实现局部更新。
Example
var MyComponent = React.createClass({
render: function() {
if (this.props.first) {
return <div className="first"><span>A Span</span></div>;
} else {
return <div className="second"><p>A Paragraph</p></div>;
}
}
});
同样的逻辑使用regularjs描述
{{#if first}}
<div className="first"><span>A Span</span></div>
{{#else}}
<div className="second"><p>A Paragraph</p></div>;
{{/if}}
仁者见仁智者见智, 反正我倾向于使用模板来描述结构,而不是杂糅Virtual dom和js语句。你呢?
值得一提的是,由于React的特性,它两次render之间,内部节点的替换是无法预计的(参考这里),所以无法有效的保留信息,所以它也有大量的关于id的placeholder存在。你可以同样查看react-todomvc生成的节点
Contrast /Solutions | String-based templating | Dom-based templating | Living templating |
---|---|---|---|
例子 | Mustache,Dustjs | Angularjs, Vuejs | Regularjs 、Ractivejs、htmlbars |
语法 | ♦♦♦ | ♦♦♦ | ♦♦♦ |
活动性 | X | ♦♦♦ | ♦♦♦ |
性能 | 初始: ♦♦♦ 更新: ♦ |
初始: ♦ 更新: ♦♦♦ |
初始: ♦ 更新: ♦♦♦ |
安全性 | ♦ | ♦♦ | ♦♦♦♦♦ |
Dom 无关 | ♦♦♦♦♦ | X | ♦♦ |
SVG support(*1) | X | ♦♦ | ♦♦♦ |
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。