原文:http://www.cnblogs.com/jingwhale/p/5811800.html
模板的工作原理可以简单地分成两个步骤:模板解析(翻译)和数据渲染。这两个步骤可分别部署在前端或后端来执行。如果放在后端执行,则是像Smarty,FreeMarker这样的后端模板引擎,而如果放在前端来执行,则是我们要探讨的前端模板。
FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写,模板用servlet提供的数据动态地生成 HTML,模板语言是强大的直观的,编译器速度快,输出接近静态HTML页面的速度。这里不再对后端模版进行描述。
前端模版提高了前端开发的可维护性(后期改起来方便)以及可扩展性(想要增加功能,增加需求方便);提高了开发效率提高(程序逻辑组织更好,调试方便);最重要的一点就是:【视图(包括展示渲染逻辑)与程序逻辑的分离】。好处是减轻服务器负担,坏处是可能不利于seo以及模版错误不好调试。
当今前端模版主要有三类:
-String-based 模板技术 (基于字符串的parse和compile过程) -Dom-based 模板技术 (基于Dom的link或compile过程) -杂交的Living templating 技术 (基于字符串的parse 和 基于dom的compile过程)。
一.前端模版的演变
传统的前端开发方式是通过通过ajax获取数据进行繁琐的数据渲染。随着前端页面的交互越来越繁杂,页面无刷新的传输与页面的显然也越发的频繁,导致页面性能低下。即当前端从后台通过ajax等方式获取到数据更新后,都需要将这个数据渲染到指定的dom元素中,需要重新进行各种字符串拼接工作或者一系列创建元素的工作,这种方式是繁琐且费时的。这种在可读性和维护性上也存在问题。
基于字符串的模板引擎最大的功劳就是把你从大量的夹带逻辑的字符串拼接中解放出来了,由于它的完全基于字符串的特性,它拥有一些无可替代的优势。如下的字符串拼接:
Dom-based的模板技术中,如果你需要从一段字符串创建出一个view,你必然通过innerHTML来获得初始Dom结构. 然后引擎会利用Dom API(attributes, getAttribute, firstChild… etc)层级的从这个原始Dom的属性中提取指令、事件等信息,Dom-based的模板技术并没有完整的parse的过程。继而完成数据与View的绑定,使其”活动化”。所以Dom-based的模板技术更像是一个数据与dom之间的“链接”和*“改写”*过程。 完成compile之后,data与View仍然保持联系,即你可以不依赖与手动操作Dom API来更新View。
String-based 和 Dom-based的模板技术都或多或少的依赖与innerHTML, 它们的区别是一个是主要是为了Rendering 一个是为了 Parsing 提取信息。Living Template Engine模版引擎的解析过程类似于String-based 模板技术 和 compile过程类似于Dom-based模板技术。
二.String-based 模板技术 (基于字符串的parse和compile过程)
抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。
实现一个简单的字符串循环模版:
上面的例子很好的说明了String-based 模板技术的原理。它产生html结构,直接通过innerHTML插入到DOM中。
优点:相对于字符串拼接,实现了模版和代码逻辑的分离,不用大量的字符串拼接 缺点:render之后数据即与view完全分离,innerHTML的性能问题,安全问题等
三.Dom-based 模板技术 (基于Dom的link或compile过程)
先通过innerHTML来获得初始Dom结构,然后引擎会利用Dom API(attributes, getAttribute, firstChild… etc)层级的从这个原始Dom的属性中提取指令、事件等信息。继而完成数据与View的绑定,使其”活动化”。
Node对象定义了一系列属性和方法,来方便遍历整个文档。用parentNode属性和childNodes[]数组可以在文档树中上下移动;通过遍历childNodes[]数组或者使用firstChild和nextSibling属性进行循环操作,也可以使用 lastChild和previousSibling进行逆向循环操作,也可以枚举指定节点的子节点。而调用appendChild()、insertBefore()、removeChild()、replaceChild()方法可以改变一个节点的子节点从而改变文档树。需要指出的是,childNodes[]的值实际上是一个NodeList对象。因此,可以通过遍历childNodes[]数组的每个元素,来枚举一个给定节点的所有子节点。
在 JavaScript 中也有很多树形结构。比如 DOM 树,省市区地址联动,文件目录等; JSON 本身就是树形结构。通过递归,可以枚举树中的所有节点。
四.Living Template Engine
String-based 和 Dom-based的模板技术都或多或少的依赖与innerHTML, 它们的区别是一个是主要是为了Rendering 一个是为了 Parsing 提取信息。所以为什么不结合它们两者来完全移除对innerHTML的依赖呢?parse和compile的过程分别类似于String-based 模板技术 和 Dom-based模板技术。
先调用Parser()模块对字符串进行解析输出AST,这个方法模板内部将包含对模板的词法分析、语法分析、构造输出AST。然后调用this.compile(AST)方法编译,这个方法里调用walkers进行递归遍历这个AST,最后输出并保存这个组件的Dom,当调用这个组件的compile(AST)方法编译,这个方法里调用walkers进行递归遍历这个AST,最后输出并保存这个组件的Dom,当调用这个组件的inject()方法就可以把这个Dom插入到页面中。
1 . Parsing
首先我们使用一个内建DSL来解析模板字符串并输出AST。
1)词法分析器又称为扫描器,词法分析是指将文本代码流解析为一个个记号,分析得到的记号以供后续的词法分析使用。 这个模块在Regular顶级模块执行过程中调用Parse模块进行语法分析前会调用Lexer词法分析模块对字符串模板进行词法分析。词法分析的主要流程如下图所示:
词法分析主要分为两部分进行,分别是Tag类型元素字符串,还有一类是JST字符串。通过全局中全局中保存一个state状态,当前解析完成后,会判断一个字符串的开头是否以“<”字符开始,如果是则进入Tag词法解析流程,如果不是则进入JST模板词法解析流程。 最后通过词法分析,将得到一个很长的数组,这个数组中装着一个个上面的词法对象,这将为之后的语法分析做下铺垫。
2)在词法分析模块部分,将解析词法分析出的词块,然后根据Regular模板语法,拼接零散的词块为具体含义的语法对象,然后输出一棵抽象语法树AST,这是进行下一步编译this.$compile()的输入。 首先要定义出这个抽象语法树每个节点对象的类型以及它含有的属性。
一一输入词法分析出来的词法块,根据这个词法块的type类型的不同来执行不同的逻辑,他们的本质都是根据当前type类型去判断,取出之后的词法块的一定个数,然后通过创建出特定的语法节点对象。
最终就成功得到了一棵由7种语法节点对象组成的抽象语法树AST。这将为之后的编译做下铺垫。
例如,在regularjs中,下面这段简单的模板字符串
会被解析为以下这段数据结构
2.Compiler
结合特定的数据模型(在regularjs中,是一个裸数据), 模板引擎层级游历AST并递归生成Dom节点(不会涉及到innerHTML). 与此同时,指令、事件和插值等binder也同时完成了绑定,使得最终产生的Dom是与Model相维系的,即是活动的.
通过上一节已经得到了一棵AST,这课抽象语法树的节点是7中节点的一种,这个时候只要通过先序遍历[17]这个AST,然后根据语法块的type类型执行不通过的构造函数创建出Dom对象即可。
以上面的模板代码的一个插值为例:”{{isLogin? ‘Login’: ‘Wellcome’}}”。一旦regularjs的引擎遇到这段模板与代表的语法元素节点,会进入如下函数处理
正如我们所见, 归功于$watch函数,一旦表达式发生改变,文本节点也会随之改变,这一切其实与angularjs并无两样(事实上regularjs同样也是基于脏检查)
与Dom-based 模板技术利用Dom节点承载信息所不同的是,它的中间产物AST 承载了所有Compile过程中需要的信息(语句, 指令, 属性…等等). 这带来几个好处
轻量级, 在Dom中进行读写操作是低效的. 可重用的. 可序列化 , 你可以在本地或服务器端预处理这个过程。 安全, 因为安全不需要innerHTML帮我们生成初始Dom。
小结:
后端模板可以承载页面的固定数据,如登陆的webUser,它随着页面的产生而产生,随着页面的消失而消失;前端模板主要实现复杂的页面交互伴随的数据变化,进行页面无刷新的数据更新,实现页面的多彩化。所以前端模板和后端模板要相互结合使用,才能更好的服务于web应用。