前端模板技术面面观(2)
此文已由作者郑海波授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验
Living Template Engine
String-based 和 Dom-based的模板技术都或多或少的依赖与innerHTML, 它们的区别是一个是主要是为了Rendering 一个是为了 Parsing 提取信息
所以为什么不结合它们两者来完全移除对innerHTML的依赖呢?
事实上,值得庆幸的是,已经有几个现实例子在这么做了。
例子
基本原理
就如图中所示,parse和compile的过程分别类似于String-based 模板技术 和 Dom-based模板技术。
下面来完整讲述下这两个过程
1 . Parsing
首先我们使用一个内建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是自治的,完全不依赖与html,你可以想像下dom-based的模板的那些语法相关的指令,事实上它们甚至无法表达上述那段简单的模板的逻辑。
Living模板技术需要同时处理dsl元素 与 xml元素来实现最终视图层的活动性,即它们是dom-aware的,而在字符串模板中其实xml元素完全可以无需关心,它们被统一视为文本元素。
2 Compiler
结合特定的数据模型(在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过程中需要的信息(语句, 指令, 属性...等等). 这带来几个好处
轻量级, 在Dom中进行读写操作是低效的.
可重用的.
可序列化 , 你可以在本地或服务器端预处理这个过程。
安全, 因为安全不需要innerHTML帮我们生成初始Dom
如果你查看Living Template的输出,你会发现是这样的
只有需要的内容被输出了
总结Living templating
我们可以发现Living templating几乎同时拥有String-based和Dom-based模板技术的优点
利用一个如字符串模板的自定义DSL来描述结构来达到了语法上的灵活性,并在Parse后承载信息(AST)。而在Compile阶段,利用AST和Dom API来完成View的组装,在组装过程中,我们同样可以引入Dom-based模板技术的诸如Directive等优良的种子。
living template's 近亲 —— React
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 | ♦♦ | ♦♦♦ |
任何一类无法被另一类全面替代
它们并不是无法同时存在的,比如你可以使用字符串模板来生成Dom-based的模板需要的模板字符串。
参考资料
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 知物由学 | AI时代,那些黑客正在如何打磨他们的“利器”?(一)
【推荐】 HBase基准性能测试报告
【推荐】 【大数据之数据仓库】选型流水记