嗯,这也是一个进修笔记,材料均来自收集,有修改。。
文章重要分为三部份,第一部份经由历程完成一个浅易的ERB模板引擎来引见其道理,参考javascript模板引擎和完成道理,有修改,看了很多关于模板引擎的文章,就这一篇最通俗易懂,合适入门。第二部份引见一个异常NB的模板引擎,参考JavaScript template engine in just 20 lines,超等简约,仅20行,合适进阶。第三部份引见一种js模板引擎——art Template,之所以引见他是由于。。。。你猜。
一.入门-浅易的ERB模板引擎
模板简介
模板平常是指嵌入了某种动态编程言语代码的文本,数据和模板经由历程某种情势的连系,可以变化出差异的结果。模板平常用来定义显现的情势,可以使得数据展示更加雄厚,而且轻易保护。比方,下面是一个模板的例子:
<ul>
<% for(var i in items){ %>
<li class='<%= items[i].status %>'><%= items[i].text %></li>
<% } %>
</ul>
假如有以下items
数据:
items:[
{ text: 'text1' ,status:'done' },
{ text: 'text2' ,status:'pending' },
{ text: 'text3' ,status:'pending' },
{ text: 'text4' ,status:'processing' }
]
经由历程某种体式格局的连系,可以发生下面的Html
代码:
<ul>
<li class='done'>text1<li>
<li class='pending'>text2<li>
<li class='pending'>text3<li>
<li class='processing'>text4<li>
</ul>
假如不运用模板,想要到达一样的结果,行将上面的数据展示成结果的模样,须要像下面如许将html
标签拼接成字符串:
var temp = '<ul>';
for(var i in items){
temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';
可以看出运用模板有以下优点:
简化了
html
的誊写经由历程编程元素(比方轮回和前提分支),对数据的展示更具有掌握的才能
星散了数据与展示,使得展示的逻辑和结果更容易保护
模板引擎
经由历程剖析模板,将数据和模板连系在一起输出末了的结果的顺序称为模板引擎,模板有很多种,相对应的模板引擎也有很多种。一种比较陈旧的模板称为ERB
,在很多的web框架中被采纳,比方:ASP.NET
、 Rails
… 上面的例子就是ERB
的例子。在ERB
中两个中心的观点:evaluate
和interpolate
。表面上evaluate
是指包含在<% %>
中的部份,interpolate
是指包含在<%= %>
中的部份。从模板引擎的角度,evaluate
中的部份不会直接输出到结果中,平常用于历程掌握;而interpolate
中的部份将直接输出到结果中。
从模板引擎的完成上看,须要依靠编程言语的动态编译或许动态诠释的特征,以简化完成和进步机能。比方:ASP.NET
应用.NET
的动态编译,将模板编译成动态的类,并应用反射动态实行类中的代码。这类完成实际上是比较复杂的,由于C#
是一门静态的编程言语,然则运用javascript
可以应用Function
,以少少的代码完成一个浅易的模板引擎。
模板文本转化
针对上面的例子,回忆一下运用模板和不运用模板的差异。
模板写法:
<ul>
<% for(var i in items){ %>
<li class='<%= items[i].status %>'><%= items[i].text %></li>
<% } %>
</ul>
非模板写法:
var temp = '<ul>';
for(var i in items){
temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';
仔细观察,实际上这两种要领非常“类似”,可以找到某种意义上的一一对应。假如可以将模板的文本变成代码实行,那末就可以完成模板转化。在转化历程当中有两个准绳:
碰到一般的文本直接当做字符串拼接
碰到
interpolate
(即<%= %>
),将个中的内容当做变量拼接在字符串中碰到
evaluate
(即<% %>
),直接当做代码
将上面的例子依据上述准绳举行变更,再增加一个总的函数:
var template = function(items){
var temp = '';
//最先变更
temp += '<ul>';
for(var i in items){
temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';
}
末了实行这个函数,传入数据参数即可:
var result = template(items);
javascript动态函数
可见上面的转化逻辑实在非常简朴,然则症结的题目是,模板是变化的,这意味着天生的顺序代码也必需是在运行时天生并实行的。幸亏javascript
有很多动态特征,个中一个壮大的特征就是Function
。
我们平常运用function
症结字在js
中声明函数,很罕用Function
。在js
中function
是字面语法,js
的运行时会将字面的function
转化成Function
对象,所以实际上Function
供应了更加底层和天真的机制。
用 Function
类直接建立函数的语法以下:
var function_name = new Function(arg1, arg2, ..., argN, function_body)
比方:
//建立动态函数
var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
//实行
sayHi('Hello','World');
函数体和参数都可以经由历程字符串来建立!So cool!有了这个特征,可以将模板文本转化成函数体的字符串,如许就可以建立动态的函数来动态的挪用了。
完成思绪
起首应用正则式来形貌interpolate
和evaluate
,括号用来分组捕捉:
var interpolate_reg = /<%=([\s\S]+?)%>/g;
var evaluate_reg = /<%([\s\S]+?)%>/g;
为了对全部模板举行一连的婚配将这两个正则式兼并在一起,然则注重,一切可以婚配interpolate
的字符串都能婚配evaluate
,所以interpolate
须要有较高的优先级:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
设想一个函数用于转化模板,输入参数为模板文本字串和数据对象
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){ ... }
运用replace
要领,举行正则的婚配和“替代”,实际上我们的目标不是要替代interpolate
或evaluate
,而是在婚配的历程当中构建出“要领体”:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){
var index = 0;//纪录当前扫描到哪里了
var function_body = "var temp = '';";
function_body += "temp += '";
text.replace(matcher,function(match,interpolate,evaluate,offset){
//找到第一个婚配后,将前面部份作为一般字符串拼接的表达式
function_body += text.slice(index,offset);
//假如是<% ... %>直接作为代码片断,evaluate就是捕捉的分组
if(evaluate){
function_body += "';" + evaluate + "temp += '";
}
//假如是<%= ... %>拼接字符串,interpolate就是捕捉的分组
if(interpolate){
function_body += "' + " + interpolate + " + '";
}
//递增index,跳过evaluate或许interpolate
index = offset + match.length;
//这里的return没有什么意义,由于症结不是替代text,而是构建function_body
return match;
});
//末了的代码应该是返回temp
function_body += "';return temp;";
}
至此,function_body
虽然是个字符串,但内里的内容实际上是一段函数代码,可以用这个变量来动态建立一个函数对象,并经由历程data
参数挪用:
var render = new Function('obj', function_body);
return render(data);
如许render
就是一个要领,可以挪用,要领内部的代码由模板的内容组织,然则大抵的框架应该是如许的:
function render(obj){
var temp = '';
temp += ...
...
return temp;
}
注重到,要领的形参是obj
,所以模板内部援用的变量应该是obj
:
<script id='template' type='javascript/template'>
<ul>
<% for(var i in obj){ %>
<li class="<%= obj[i].status %>"><%= obj[i].text %></li>
<% } %>
</ul>
</script>
看似到这里就OK了,然则有个必需处置惩罚的题目。模板文本中能够包含\r
\n
\u2028
\u2029
等字符,这些字符假如出现在代码中,会失足,比以下面的代码是毛病的:
temp += '
<ul>
' + ... ;
注重:javascript 中的字符串是不能跨行的!
我们愿望看到的应该是如许的代码:
temp += '\n \t\t<ul>\n' + ...;
如许须要把\n
前面的\
转义成\\
即可,终究变成字面的\\n
。
别的,另有一个题目是,上面的代码没法将末了一个evaluate
或许interpolate
背面的部份拼接进来,处置惩罚这个题目的方法也很简朴,只须要在正则式中增加一个行尾的婚配即可:
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g;
相对完全的代码
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g
//模板文本中的特别字符转义处置惩罚
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\t': 't',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){
var index = 0;//纪录当前扫描到哪里了
var function_body = "var temp = '';";
function_body += "temp += '";
text.replace(matcher,function(match,interpolate,evaluate,offset){
//找到第一个婚配后,将前面部份作为一般字符串拼接的表达式
//增加了处置惩罚转义字符
function_body += text.slice(index,offset)
.replace(escaper, function(match) { return '\\' + escapes[match]; });
//假如是<% ... %>直接作为代码片断,evaluate就是捕捉的分组
if(evaluate){
function_body += "';" + evaluate + "temp += '";
}
//假如是<%= ... %>拼接字符串,interpolate就是捕捉的分组
if(interpolate){
function_body += "' + " + interpolate + " + '";
}
//递增index,跳过evaluate或许interpolate
index = offset + match.length;
//这里的return没有什么意义,由于症结不是替代text,而是构建function_body
return match;
});
//末了的代码应该是返回temp
function_body += "';return temp;";
var render = new Function('obj', function_body);
return render(data);
}
挪用代码可所以如许:
<script id='template' type='javascript/template'>
<ul>
<% for(var i in obj){ %>
<li class="<%= obj[i].status %>"><%= obj[i].text %></li>
<% } %>
</ul>
</script>
...
var text = document.getElementById('template').innerHTML;
var items = [
{ text: 'text1' ,status:'done' },
{ text: 'text2' ,status:'pending' },
{ text: 'text3' ,status:'pending' },
{ text: 'text4' ,status:'processing' }
];
console.log(template(text,items));
遗留的题目
另有几个细节的题目须要注重:
由于
<%
或许%>
都是模板的边境字符,假如模板须要输出<%
或许%>
,那末须要设想转义的方法假如数据对象中包含有
null
,明显不愿望末了输出null
,所以须要在function_body
的代码中斟酌null
的状况在模板中每次运用obj的形参援用数据,能够不太轻易,可以在
function_body
增加with(obj||{}){...}
,如许模板中可以直接运用obj
的属性可以设想将
render
返回出去,而不是返回转化的结果,如许外部可以缓存天生的函数,以进步机能
二.进阶-超简约js模板引擎
js模板引擎
var TemplateEngine = function(html, options) {
var re = /<%([^%>]+)?%>/g, reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g, code = 'var r=[];\n', cursor = 0, match;//1
var add = function(line, js) {//2
js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
return add;
}
while(match = re.exec(html)) {//3
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
add(html.substr(cursor, html.length - cursor));
code += 'return r.join("");';
return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);//4
}
诠释
标注1
re = /<%([^%>]+)?%>/g
该正则表达式用于猎取模板中的标识字段<%...%>
,然后用传入引擎中的数据去添补它们.
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g
该正则的作用是,假如一段JS代码以if, for, else, switch, case, break, |
开首,那它们将会直接增加到函数体中。假如不是,则会被push到code变量中。
假如你看不懂上述正则表达式,请参考这里 js正则表达式语法
标注2
var add = function(line, js) {
js? (code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n') :
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
return add;
}
js模板由三部份构成
1.一般文本字符串,比方标签元素
ul
等
2.js流程掌握语句,比方if, for, else
等等,对应ERB模板中的interpolate
3.变量,须要用输入的数据替代的部份,对应ERB模板中的evaluate
注重:有的将模板分为两部份,js代码(对应2)和文本(对应1、3)
解释2中add函数的作用是天生衬着函数的函数体字符串(Function函数体字符串),该函数终究返回HTML文档字符串。模板中的一切部份都须要拼接构成函数体字符串,但依据是不是须要拼接到HTML文档字符串中,须要对差异部份实行差异的操纵。js代码作为流程掌握语句,不须要拼接到HTML文档字符串中(函数返回的字符串中不会包含该部份),直接作为代码实行,而关于文本须要拼接成HTML文档字符串,具体剖析以下:
解释:拼接HTML文档字符串有两种体式格局,一种是应用+
运算符,另一种是将须要拼接到HTML文档字符串的部份push
到数组中,然后应用’join’函数兼并。
1.关于一般文本字符串,须要作为字符串拼接到HTML字符串中,此时,js取
false
,将实行
(code += line != '' ? 'r.push("' + line.replace(/"/g, '\\"') + '");\n' : '');
注重:push
到数组中的内容须要被""
围困的,因而假如一般文本中包含有"
,须要转译为\\"
2.关于流程掌握语句和变量,他们都是被标识标记
<%%>
围困的部份,对应的js去true
,将实行
(code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n')
1)关于js流程掌握语句,
line.match(reExp)
为true
,不须要拼接到HTML文档字符串中,因而不须要push
到数组中,而直接拼接到函数体字符串中。
2)关于变量,须要拼接到HTML文档字符串中,然则不能被""
围困,否则会作为字符串输出,而不会作为变量。
别的:注重add函数末了又返回了本身,类似于js中的链式操纵。
标注3
while(match = re.exec(html)) {
add(html.slice(cursor, match.index))(match[1], true);
cursor = match.index + match[0].length;
}
这里须要相识exec
函数,不晓得请移步这里JavaScript exec() 要领
标注4
return new Function(code.replace(/[\r\t\n]/g, '')).apply(options);
应用apply
设置Function
函数作用域,在函数内部,this
指向options
,可以应用this.attr接见options.attr。
三.art Template
简介
javascript模板引擎是数据与界面星散事情中最重要一环。虽然每一个引擎从模板语法、语法剖析、变量赋值、字符串拼接的完成体式格局各有所差异,但症结的衬着道理仍然是动态实行 javascript 字符串。
artTemplate 是新一代 javascript 模板引擎,它采纳预编译体式格局让机能有了质的奔腾,而且充分应用 javascript 引擎特征,使得其机能不管在前端照样后端都有极为精彩的表现。
art Template高效的隐秘
预编译
在平常的模板引擎完成道理中,由于要对模板变量举行赋值,所以每次衬着都须要动态编译 javascript 字符串完成变量赋值。而 artTemplate 的编译赋值历程倒是在衬着之前完成的,这类体式格局称之为“预编译”。
更快的字符串拼接体式格局
很多人误以为数组 push
要领拼接字符串会比 +=
快,要知道这仅仅是 IE6-8
的浏览器下。实测表明当代浏览器运用 +=
会比数组 push
要领快,而在 v8
引擎中,运用 +=
体式格局比数组拼接快 4.7
倍。所以 artTemplate
依据 javascript
引擎特征采纳了两种差异的字符串拼接体式格局。
运用要领
援用js文件:<script src=”js/arttmpl.js”></script>
页面中,运用一个type=”text/template”的script标签寄存模板:
<script id='doctor-template' type="text/template">
<% for(var i in data){ var item=data[i]; %>
<li class="mui-table-view-cell mui-media ">
<a href="javascript:;" class="mui-navigate-right">
<div class="mui-media-object mui-pull-left">
<img src="<%=(item.photo)%>" width="100%">
<span class="mui-badge mui-badge-danger"><%=(item.score)%>分</span>
</div>
<div class="mui-media-body">
<%=(item.name)%> <span class="mui-badge mui-badge-warning"><%=(item.position)%></span>
<p class="mui-ellipsis"><%=(item.hospital)%></p>
<p class="mui-ellipsis"><%=(item.desc)%></p>
</div>
</a>
</li>
<% } %>
</script>
模板逻辑语法最先与完毕的界定标记为<%
与 %>
,若<%
背面紧跟=
号则输出变量内容。同ERB模板
3.衬着模板
template.render(id, data);
继承上面的例子:
var fragment = template('doctor-template', {
"data":[
{
name:"王静",
score:4.5,
photo:'images/logo.png',
hospital:"江苏省中病院",
desc:'妇科、不孕不育症、月经病等',
position:'副医师'
},
{
name:"啦啦",
score:4.9,
photo:'images/logo.png',
hospital:"鼓楼病院",
desc:'儿童呼吸系统疾病的诊治,关于儿童疾病',
position:'主治医师'
}
]
});
四.参考
1.javascript 模板引擎系列文章(一)
2.JavaScript模板引擎道理,几行代码的事儿
3.高机能JavaScript模板引擎道理剖析
4.javascript模板引擎和完成道理
5.js模板引擎——art Template
6.[译] 只要 20 行的 JavaScript 模板引擎
7.javascript模板引擎和完成道理
8.引荐五款盛行的JavaScript模板引擎