模板简介
模板平常是指嵌入了某种动态编程言语代码的文本,数据和模板经由历程某种情势的连系,可以变化出差异的结果。模板平常用来定义显现的情势,可以使得数据展示更加雄厚,而且轻易保护。比方,下面是一个模板的例子:
<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>
假如不运用模板,想要到达一样的结果,行将上面的数据展示成结果的模样,须要像下面如许做:
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,以少少的代码完成一个浅易的模板引擎。本文就来完成一个浅易的ERB模板引擎,以展示javascript的壮大的地方。
模板文本转化
针对上面的例子,回忆一下运用模板和不运用模板的差异:
模板写法:
<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>
' + ... ;
我们愿望看到的应该是如许的代码:
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
返回出去,而不是返回转化的结果,如许外部可以缓存天生的函数,以进步机能