这次的模板引擎主要在算法方面做了些改进,主要借鉴正美的思路,
https://github.com/RubyLouvre/mass-Framework/issues/22
传统的字符串模块都要处理转义的问题,但我这新方法通过传参手段消灭了这步骤
核心原理
function a(){ var b = ["1,2,3"] return Function("b", "function test(){}; return function(data){ test(); return b }")(b) } a()+""
主要原理是根据模版生成的字符串构造函数对字符串的转义要求很高,一不留神就会出错,这次采用了将需要转义的字符串存入闭包中来进行引用,这样就很好的避免字符串转义的操作。
同时也极大的减少了最后new Function时的函数体。
下面这段代码体现出核心原理
self.__lines[i]=v;
self.body.push('\ttemp.push(this.__lines['+(i++)+']);\n');
之前的版本是这样的self.body.push(
'\ttemp.push(\"'
+v.replace(/\n|\t/g,
''
)+
'\");\n'
);那么变量v必须得转义一次了(此步操作之前就得转义)。
该模板优点:
1.模板采用js语法,没有学习成本
2.也是由于优点1所以该模板的解析速度还是很占优势的
3.可以自定义模板分隔符,就算是与js语法冲突的{{ }}都没有问题,避免了和后端模板分隔符冲突的风险
4.引入helper机制,避免影响全局变量
5.内置缓存,每个模板只会编译一次,生成的模板函数会对其缓存
//helper机制的使用
var tpl = Template(options, helper);
options是一个对象,有以下属性
title: function(){
return "<p>这是使用视图helper输出的代码片断</p>"
},
handle:function(data){
return data.name.replace("aaa","bbb");
}
}
(function(w){
w.Template=Template||{};
function Template(options,helper){
return this instanceof Template?this.init(options,helper):new Template(options,helper);
}
Template.parse=function(self){
if(!self.__lines){
self.__lines=[];
}
var temp,i=0;
if(self.right=="}}"){//这里是为了解决}}}造成的bug!
temp=self.tpl.replace(/(}})([^}])/g,"$1 $2").split(new RegExp('(?='+self.left+')|('+self.right+')(?:[^}])'))
}else{
temp=self.tpl.split(new RegExp('(?='+self.left+')|('+self.right+')'))
}
temp.filter(function(k,v){
return !(new RegExp(self.right)).test(v);
}).each(
function(k,v){
if((new RegExp('^'+self.left)).test(v)){
v=v.replace('@','data.');
if(new RegExp('^'+self.left+'\s*=').test(v)){
self.body.push(v.replace(new RegExp('^'+self.left+'\s*=(.*)'),'\ttemp.push($1);\n'));
}else{
self.body.push(v.replace(new RegExp('^'+self.left+'\s*(.*)'),'$1\n'));
}
}
else {
self.__lines[i]=v;
self.body.push('\ttemp.push(this.__lines['+(i++)+']);\n');
}
})
return self.body.join("");
};
Template.prototype={
init:function(options,helper){
this.tpl=options.tpl;
this.left=options.left||"{{";
this.right=options.right||"}}";
this.body=[];
this.compiled=null;
this.data=options.data;
this.helper=helper;
},
compile:function(){
if(!this.compiled){
var helper=[];
if(this.helper){
for(var h in this.helper){
helper.push('var '+h+'=this.helper["'+h+'"]');
}
}
this.compiled=new Function("data",helper.join(";")+';var temp=[];\n'+Template.parse(this)+'\n return temp.join("");');
}
return this.compiled;
},
render:function(data){
return this.compile().call(this,data||this.data);
}
}
})(this);
Array.prototype.filter=function(fn){
var temp=[];
for(var i=0,l=this.length;i<l;i++){
this[i]&&fn.call(this,i,this[i])&&temp.push(this[i]);
}
return temp;
}
Array.prototype.each=function(fn){
var temp=[];
for(var i=0,l=this.length;i<l;i++){
fn.call(this,i,this[i]);
}
return this;
}
下面是示例代码
<script type="tmpl" id="table_tmpl">
<&= title() &>
<table border=1>
<& for(var i=0,tl = @trs.length,tr;i<tl;i++){ &>
<& tr = @trs[i]; &>
<tr>
<td><&= tr.name&></td> <td><&= tr.age&></td> <td><&= tr.sex || '男' &></td>
</tr>
<& } &>
</table>
<img src="<&= @href &>">
</script>
<script>
var trs = [
{name:"隐形杀手",age:29,sex:"男"},
{name:"索拉",age:22,sex:"男"},
{name:"fesyo",age:23,sex:"女"},
{name:"恋妖壶",age:18,sex:"男"},
{name:"竜崎",age:25,sex:"男"},
{name:"你不懂的",age:30,sex:"女"}
]
var html = Template({
tpl:document.getElementById("table_tmpl").text,
left:"<&",
right:"&>",
data:{
trs: trs,
href: "http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_type4.jpg"
}
},{
title: function(){
return "<p>这是使用视图helper输出的代码片断</p>"
}
});
document.getElementById("test123").innerHTML=html.render()
</script>
下面是输出结果
这是使用视图helper输出的代码片断
隐形杀手 | 29 | 男 |
索拉 | 22 | 男 |
fesyo | 23 | 女 |
恋妖壶 | 18 | 男 |
竜崎 | 25 | 男 |
你不懂的 | 30 | 女 |

生成的匿名函数代码
function anonymous(data) {
var title = this.helper.title;
var handle = this.helper.handle;
var cut = this.helper.cut;
var temp = [];
temp.push(this.__lines[0]);
temp.push(title());
temp.push(this.__lines[1]);
temp.push(handle(data.a));
temp.push(this.__lines[2]);
for (var i in data.a) {
temp.push(this.__lines[3]);
temp.push(i);
temp.push(this.__lines[4]);
temp.push(data.a[i]);
temp.push(this.__lines[5]);
}
temp.push(this.__lines[6]);
if (data.b == 100) {
temp.push(this.__lines[7]);
temp.push(data.b);
temp.push(this.__lines[8]);
} else {
temp.push(this.__lines[9]);
}
temp.push(this.__lines[10]);
for (var i = 0, l = data.song.length; i < l; i++) {
temp.push(this.__lines[11]);
temp.push(data.song[i].songname);
temp.push(this.__lines[12]);
temp.push(data.song[i].singer);
temp.push(this.__lines[13]);
temp.push(data.song[i].url);
temp.push(this.__lines[14]);
temp.push(cut(data.song[i].url));
temp.push(this.__lines[15]);
}
temp.push(this.__lines[16]);
for (var i = 0, l = data.url.length; i < l; i++) {
temp.push(this.__lines[17]);
temp.push(data.url[i]);
temp.push(this.__lines[18]);
}
temp.push(this.__lines[19]);
return temp.join("");
}