一个对前端模板技术的全面总结
此文的写作耗时很长,称之为雄文不为过,小心慢用
此文缘由
其实从发布regularjs之后,我发现在google搜索regularjs 不是给我这个画面
<!-- more -->
就是给我这个画面
突然发现取名字真是个大学问,当时就基本预计到了会有不明真相的朋友认为它只是一个照搬angularjs的家伙,对于这点,有兴趣的朋友可以看下【为什么要造Regularjs这个轮子】。
而在这个文章,我不会直截了当去与angular做直接的对比,而是从最基本原理开始对现有的模板解决方案进行一个全面的分类,同时会给出它们的一些或优或劣的特性,这些特性基本都是本质性的,即它不为维护者的水平高低和勤勉与否所限制,所以是具有客观性的。
什么是模板解决方案?
你可以先简单的理解为模板引擎。
事实上前端的模板解决方案已经从 “选出一个好用的模板好难” 发展到了 “从这么多模板中选一个好难的”的阶段,Template-Engine-Chooser!似乎也开始无法跟上节奏了。再加上目前Dom-based的模板技术的崛起(angularjs, knockout等),渐渐让这个领域有乱花渐欲迷人眼的感觉。
这篇文章会对当今前端界的三种截然不同的模板方案做一个全面的对比,它们分别是
- String-based 模板技术 (基于字符串的parse和compile过程)
- Dom-based 模板技术 (基于Dom的link或compile过程)
- 杂交的Living templating 技术 (基于字符串的parse 和 基于dom的compile过程)
同种类型的模板技术的可能性都是相同的,即同样身为dom-based的vuejs如果愿意可以发展为angularjs的相同功能层级。
(注: 其实这么说作者后续思考后觉得并不是很妥当,因为决定这类框架的还有重要一环就是它们的数据管理层:,比如是基于脏检查还是基于setter和getter,就会有截然不同的定位)
另外需要注意的是任何一种类型的模板技术都是不可被替代的,它们甚至可以结合使用,并且很长一段时间内还会继续共存下去。
除此之外另外一种奇葩模板技术本文也会提到即react,了解后你会发现它的特性更接近于Living templating。
在进入介绍之前,我们需要先过一下不得不说的 InnerHTML,它是本文的关键因素。
innerHTML
我不认为还需要从innerHTML
的细节讲起,我们对它太熟悉了,那就直接从优劣开始讲吧!
innerHTML 毫无疑问是好的
在innerHTML
正是成为 web 标准 前,它当之无愧的已经是大家公认的事实标准,这是因为:
1 . 它便于书写并且直观
想象下你必须添加如下的html到你的文档里
<h2 title="header">title</h2>
<p>content</p>
直接使用 innerHTML
node.innerHTML = "<h2 title="header">title</h2><p>content</p>"
在对比使用Dom API
var header = document.createElement('h2');
var content = document.createElement('p');
h2.setAttribute('title', 'header');
h2.textContent = 'title';
p.textContent = 'content';
node.appendChild(header);
node.appendChild(content);
innerHTML
毫无疑问赢得了这张比赛.
尽管部分框架例如mootools:Element 提供了高效的API来帮助你创建dom结构,innerHTML
仍然会是大多数人的最佳选择
2 . 它很快,特别在old IE
随着浏览器的发展,这个测试可能越来越不符合实际,
innerHTML
和Dom Level 1
创建dom结构的差距正变得原来越小
3. 它完成进行了String -> Dom的转换
这个论点有点拗口,事实上后续要提到的两类模板技术都是因为这个特点而与其有了强依赖
然而我们又清楚的被告知:
The recommended way to modify the DOM is to use the DOM Level 1 API. ——Chapter 15 of "Javascript: The Definitive Guide_"
为什么?
innerHTML 有时候又是不听话的
1. 安全问题
innerHTML
具有安全隐患.,例如:
document.body.innerHTML = "<img src=x onerror='alert(xss)'/>"
我知道像你这样优秀的程序员不会写出这样的代码,但当html片段不完全由你来控制时(比如从远程服务器中),这会成为一个可能引爆的炸弹。
2. 它很慢
等等,你刚才说了它很快! 是的,但是如果你仅仅为了替换一个属性而用innerHTML
替换了所有的Dom节点,这显然不是一个明智的决定,因为你深知这是低效的。所以说:
Context is everything
所有离开背景谈的性能、功能、性功能都是伪科学
3. 它很笨
它会完全移除所有现有的Dom,并重新渲染一遍,包括事件和状态都以不复存在,这点利用innerHTML来进行render的框架(例如Backbone)的开发者应该深有体会,为了减少损失,不能不把View拆的越来越细,从而抱着看似“解耦完美”的架构体系进入了维护的深渊。
注: 其实react的最大贡献就是它差不多是提供了一个更smart的innerHTML解决方案。
4. 有可能会创建出意料之外的节点.
由于html的parser非常的“友好”, 以至于它接受并不规范的写法,从而创建出意料之外的结构,而开发者得不到错误提示。
好了,到现在为止,我们大概了解了innerHTML
这个朝夕相处的小伙伴,接下来我们正式聊一聊模板技术,首先我们从最常见的“String-based templating”开始
String-based templating
基于字符串的模板引擎最大的功劳就是把你从大量的夹带逻辑的字符串拼接中解放出来了,由于它的完全基于字符串的特性,它拥有一些无可替代的优势。
It is essentially a way to address the need to populate an HTML view with data in a better way than having to write a big, ugly string concatenation expression. --- cited from http://www.dehats.com/drupal/?q=node/107
示例
- mustache及其衍生: 弱逻辑
- Dust.js: 强逻辑 (推荐)
- doT.js: 超级快
基本原理
如上图所示,我们发现字符串模板强依赖于innerHTML
(渲染), 因为它的输出物就是字符串。由于这篇文章的重点不在这里,我们不会再对它们如何使用作深究。
优点
- 快速的初始化时间: 很多angular的簇拥者在奚落String-based templating似乎遗漏了这一点。
- 同构性: 完全的dom-independent,即可作为用服务器端和浏览器端(客官先不要急着搬phantomjs哈).
- 更强大的语法支持:因为它们都是不是自建DSL就是基于JavaScript语法,Parser的灵活性与受限于HTML的Dom-based模板技术不可同日而语
缺点
- 安全隐患: 见
innerHTML
- 性能问题:见
innerHTML
. - 不够聪明: 见
innerHTML
(呵呵),除此之外render之后数据即与view完全分离。
尽管在这几年的发展之下,由于异常激烈的竞争,基于字符串的前端模板技术变得越来越快,但是它们显然大部分都遗漏了一些问题
- 大侠们你们没有考虑进把输出字符串加载到Dom的时间,这才是真正瓶颈之一
- 不在相同功能前提下的对比有意义么?
Dom-based Template Engine
近几年,借着Angularjs的东风,Dom-based的模板技术开始大行其道,与此同时也出现了一些优秀的替代者,就我们国人而言,近的就有@尤小右的Vuejs 和 司徒大大的avalonjs。看仓库就可以发现风格也是完全不同:1) 一个简洁优雅 2)一个奔放不羁
示例
- Angularjs: 都28000star了还需多说么
- Knockout: 在此领域内,对Web前端而言是鼻祖级的
大致流程
Dom-based的模板技术事实上并没有完整的parse的过程(先抛开表达式不说),如果你需要从一段字符串创建出一个view,你必然通过innerHTML
来获得初始Dom结构. 然后引擎会利用Dom API
(attributes
, getAttribute
, firstChild
… etc)层级的从这个原始Dom的属性中提取指令、事件等信息,继而完成数据与View的绑定,使其”活动化”。
所以Dom-based的模板技术更像是一个数据与dom之间的“链接”和*“改写”*过程。
注意,dom-based的模板技术不一定要使用innerHTML
,比如所有模板都是写在入口页面中时, 但是此时parse过程仍然是浏览器所为。
优点
- 是活动的: 完成compile之后,data与View仍然保持联系,即你可以不依赖与手动操作
Dom API
来更新View - 是运行时高效的: 可以实现局部更新
- 指令等强大的附属物帮助我们用声明式的方式开发APP
缺点
- 部分请见innerHTML
- 没有独立的Parser,必须通过innerHTML(或首屏)获取初始节点,即它的语法是强依赖与HTML,这也导致它有潜在的安全问题
- 信息承载于属性中,这个其实是不必要和冗余的。 部分框架在读取属性后会通过诸如
removeAttribute
的方式移除它们,其实这个不一定必要,而且其实并无解决它们Dom强依赖的特性,比如如果你查看[angular的todomvc]的节点,你会发现它的输出是这样的: - FOUC(Flash of unstyled content):即内容闪动,这个无需多说了,只怪它初次进入dom的内容并不是最终想要的内容。
Living Template Engine
String-based 和 Dom-based的模板技术都或多或少的依赖与innerHTML
, 它们的区别是一个是主要是为了Rendering 一个是为了 Parsing 提取信息
所以为什么不结合它们两者来完全移除对
innerHTML
的依赖呢?
事实上,值得庆幸的是,已经有几个现实例子在这么做了。
例子
- htmlbar: 运行在handlebar之后的二次编译
- ractivejs: 独立
- Regularjs 独立, 本文作者结精之一
基本原理
就如图中所示,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 |
1. 任何一类无法被另一类全面替代 2. 它们并不是无法同时存在的,比如你可以使用字符串模板来生成Dom-based的模板需要的模板字符串。
参考资料
- Template Engines by @Sendhil
- string-templating-considered-harmful
原文发布时间:2015年08月07日
作者:拴萝卜的棍子
本文来源:前端乱炖 如需转载请联系原作者
一个对前端模板技术的全面总结相关推荐
- 前端模板技术的全面总结
此文缘由 其实从发布regularjs之后,我发现在google搜索regularjs 不是给我这个画面 <!-- more --> 就是给我这个画面 突然发现取名字真是个大学问,当时就基 ...
- JavaScript设计前端模板初步理解
模板这个词,可能很多人第一印象是后端的技术(Smarty,Velocity等),但本文要讲的却不是后端的概念,而是前端开发中所使用到的一种技术,也就是"前端模板"技术. 模板的工作 ...
- html网页设计期末大作业——化妆品展示静态模板(9页) web前端开发技术 web课程设计 网页规划与设计
html网页设计期末大作业--化妆品展示静态模板(9页) web前端开发技术 web课程设计 网页规划与设计 常见网页设计作业题材有 个人. 美食. 公司. 学校. 旅游. 电商. 宠物. 电器. 茶 ...
- freemarker中运算符_如何在Web应用系统表示层开发中应用Velocity模板技术
软件项目实训及课程设计指导--如何在Web应用系统表示层开发实现中应用Velocity模板技术 1.分离Web表示层的数据处理和展现逻辑的常见的应用技术 分离Web表示层的数据处理和展现逻辑是目前企业 ...
- 个人博客前端模板_博客设计——概述
在目前已有的许多社交软件及平台中,博客始终保持着自己的地位,始终有一大批学生和各行各业职业人员喜欢通过博客的方式记录和分享自己在生活中或者学术上等各方面的经验,这不仅方便发表者个人今后解决类似的问题, ...
- SpringBoot下的模板技术Thymeleaf详解
Thymeleaf是一个流行的模板引擎,该模板引擎采用Java语言开发. 模板引擎是一个技术名词,是跨领域跨平台的概念,在Java语言体系下有模板引擎,在C#.PHP语言体系下也有模板引擎,在Java ...
- jquery template.js前端模板引擎
作为现代应用,ajax的大量使用,使得前端工程师们日常的开发少不了拼装模板,渲染模板 在刚有web的时候,前端与后端的交互,非常直白,浏览器端发出URL,后端返回一张拼好了的HTML串.浏览器对其进行 ...
- 一句话讲明白 WebAssembly、微前端等技术背后的核心
一句话讲明白 WebAssembly.微前端等技术背后的核心 WebAssembly 是在浏览器端可执行的字节码,主要解决的问题是性能.编辑器能把 C.C++.Go.TS 等语言编译成 WebAsse ...
- JS之模板技术(aui / artTemplate)
artTemplate是个好东西啊,一个开源的js前端模板引擎,使用简单,渲染效率特别的高. 我经常使用这个技术来在前端动态生成新闻列表,排行榜,历史记录等需要在前端列表显示的信息. 下面是artTe ...
最新文章
- 分享个网盘,个人觉得很不错!
- log.net的应用示例(日志)
- python 创建json_使用Django和Python创建Json response的方法
- C# 声明方法关键字
- 详细了解为什么支持Postman Chrome应用程序已被弃用?
- java如何实现克隆接口_java之实现clone接口的详解,克隆一个对象--对应有浅克隆和深克隆,概念结合代码深入理解...
- sourcetree合并分支_不会git命令,没关系啊,还有强大的图形界面工具SourceTree
- Python下安装LDA模块,学习使用
- 闪存flash进阶知识
- grunt的基本使用
- C语言数据结构-程序设计与算法基础II-电大同步进度
- (20201209已解决)从window访问wsl地址
- 进程管理工具-Supervisord +CeSi
- 北京矿大机电信息学院计算机专业,2018年中国矿业大学(北京)机电与信息工程学院免试攻读研究生初步名单公示...
- execute、executeQuery、executeUpdate的用途及返回值
- react-router(路由)
- 标题类、文本类、列表类、代码类、图文样式、表格布局、
- HTML5期末大作业:电商购物网站设计——红色的服装商城B2C网站(40页) HTML+CSS+JavaScript 电商购物功能齐全 dw网页设计 大学生商城购物网站
- CRC校验原理的完整学习
- Carpenter's Complete Guide to the SAS REPORT Procedure
热门文章
- linux用mame玩游戏,Ubuntu下用kxmame运行精彩的街机游戏
- html转义 在线,HTML转义工具 [Javascript版]
- 统计字符串中每个字符的个数_C++程序设计——统计数字字符个数
- c++开发安卓_诺基亚科普,安卓更新为什么那么慢?
- java+读取source资源_如何从JavaJAR文件中读取资源文件?
- 大学计算机试卷分析报告,(最新整理)大学试卷分析报告
- 基于android的视频采集系统的设计与实现,基于Android的视频采集系统的设计与实现...
- 设置相机的距离_讲对焦(四):相机对焦有哪些小技巧?
- vue 指令 v-on
- python \__class__