不要重复发明轮子,这是我听到最多的一句话,而且现在有很多优秀的模板引擎:handlebar、ejs、artTemplate...那么为什么还要自己实现一个呢?原因不外乎有两个,
一来是手痒,二来是满足一点小小的虚荣心:看,模板引擎我也会,简单!感觉非常优(zhuang)秀(bi)。
既然是自己动手,那么网上的教程肯定先放一边,突然有点耗子啃南瓜——无从下口的感觉...
一切从需求出发
从后台拿到数据,拼接成字符串放在页面中,这是我们初入前端时常要做的工作,特别是遇到结构稍微复杂的页面,光拼接字符串都能搞得你一脸懵逼、二脸懵逼,终于
有一天遇到模板引擎,一边惊为天人,一边暗自骂自己傻逼。那么,今天我们动手实现的模板引擎,就从那最初的那一天开始吧!
字符串模板
话说有天接到需求,需要将一组JSON数据,渲染到页面中。如下所示:
var data = [
{ text: 'text1' ,status:'done' },
{ text: 'text2' ,status:'pending' }
];
var tpl = '<ul>'+
'<%for(var i = 0, len = data.length; i < len; i++) {%>'+
'<li class="<%= data[i].status%>"><span><%= data[i].text%></span></li> '+
'<%}%>'+
'</ul>';
最初的渲染函数
机智如我自然想到用函数来循环。。。
var render = function(data) {
var tmp = '';
tmp += '<ul>';
for(var i = 0, len = data.length; i < len; i++) {
tmp += '<li class="'+ data[i].status +'">'+data[i].text+'</li>';
}
tmp += '</ul>';
return tmp;
};
目前来讲,我们返回了渲染好的字符串,而且看来工作的很顺利。但如果将字符串增加点内容,这个函数就GG思密达了。由此看来,我们需要把字符串模板单独提取出来,然后再
进行数据渲染。
牛B的Function
我们用的最多的就是 function 关键字了,但对于 function 的爸爸 Function 却有点陌生,那么 Function 究竟哪里流弊呢?红宝石书不是建议我们不要用 Function吗?
其实,在JS中,但我们使用 function 声明函数的时候,JS会自动调用 Function 来生成实例。并且,Function 为我们提供了更强大的武器——动态函数。
语法
var function_name = new Function(arg1, arg2, ..., argN, function_body)
等同于
var function_name = function(arg1,..., argN) {function_body}
于是,我们就有了一把强力的武器,将动态的字符串,放在动态的函数中执行了。
提取字符串构造函数体
有了前面的知识基础,这一步,我们就要把 tpl 中的字符串,变成 render 的函数体。这就需要另一把武器——正则表达式。利用它,来找到需要渲染数据的位置。
var reg = /<%([sS]+?)%>/g
然后,通过 replace 方法替换 reg 找到的位置,构造成函数体!
var template = function(tpl) {
var reg = /<%([sS]+?)%>/g;
// index 用来记录替换的位置
var index = 0;
// 需要构造的函数体(一步一步和上面的render函数对比)
var func_body = "var tmp = '';";
func_body += "tmp += '";
tpl.replace(reg, function(match, val, offset, str){
// 每一次匹配到后,截取当前匹配位置和上一次匹配完成后位置之间的字符串
func_body += tpl.substring(index, offset);
// 根据 %= 判断如何进行拼接函数体
if(match.indexOf('%=') < 0) {
func_body +="';" + val + ";tmp += '";
} else {
func_body += "' + " + val.replace('=', '').trim() + "+'";
}
// 完成一次match,改变index 的值
index = offset + match.length;
return index;
});
// 完成所有匹配后,将剩下的字符串加入
func_body += tpl.substring(index);
// 返回 tmp
func_body += "';return tmp;";
return func_body;
};
现在,只要我们调用 template 函数,就会返回如 render 的函数体类似的字符串。要使template 函数返回的字符串运行起来,就要用到 Function 了。
var tmpEngine = function (tpl, data) {
// 返回字符串函数体
var func_body = template(tpl);
// 通过 Function 运行
return new Function('data', func_body).call(null, data);
};
于是,我们调用 tmpEngine, 就可以得到经过数据渲染后的字符串了。
var m = render(tpl2, data2);
console.log('m:' +m);
// m: <ul><li class="done"><span>text1</span></li> <li class="pending"><span>text2</span></li> </ul>
至此,我们的模板引擎的功能层面已经完成,可以愉快的玩耍了。但是!还有很多优化工作等待着推进,这里罗列几条,周末再战:
- 特殊字符转义,业务可能需要输出html代码,减少XSS攻击
- 数据为空时的处理
- 性能
......