JS模板引擎实现
早的时候我得到服务器返回的json数据 然后用jQuery拼接起来生成DOM插入到页面中去。
但是对于复杂的数据,拼字符串是件很头疼的事,为了方便开发,而且更具有通用性
所以就出现了很多的模板引擎 例如Mustache doT juicer Underscore Handlebars 等。
然后国内就有了xtemplate(淘宝) artTemplate(腾讯) baiduTemplate(百度) 等。
用在Node.js的例如有Jade EJS Nunjucks 等。
功能大多相同,各有特色。主要看以下方面
1.文件大小
2.执行速度
3.语法简明/易用/灵活/自定义操作符
4.错误处理/调试
而我也想尝试弄一个 其实也不难,没好意思说自己的有什么好的 也许简单就是好吧
首先 看一个最简单的 利用正则表达式替换
var re = /<%([^%>]+)?%>/g; //它的意思是匹配<%开头 中间的子匹配是^%>有至少一次 然后匹配%>结束
然后对传递进来的参数 例如 var tpl=Hello, my name is <%name%>. 执行match = re.exec(tpl) 则match的结果如下
["<%name%>", "name", index: 21, input: "Hello, my name is <%name%>."]
把
match[1]
替换成我们想要的变量就可以了。
以上的对于简单的替换就足够了 但是还是过于简单了 例如我们想要执行for循环怎么办 如果能够在<%和%>之间直接使用Javascript代码就最好了
那么在JavaScript你一定听说过Function
它的构造方法可以传入一段字符串作为函数的内容,例如
var body = '(function(){return data;}())'; var func = new Function('myFun', 'data', body); console.log(func('', 1));
它会把最后一个参数作为函数体 前面的作为形参
所以我们打算把字符串传递给new Function()来执行。
所以应该分辨js代码 和 普通字符串
var _compile = function (tpl, data) {
var re = /<%([^%>]+)?%>/g,
//应该被立即执行的语句
reExp = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g,
code = 'var r=[];\n',
//利用游标来记录当前位置match[index]
cursor = 0;
var add = function (line, js) {
//对于满足reExp的应该立即执行 不被添加到程序字符串中去:变量插入:普通字符串
js ? code += line.match(reExp) ? line + '\n' : 'r.push(' + line + ');\n' :
code += 'r.push("' + line.replace(/"/g, '\\"') + '");\n';
};
while (match = re.exec(tpl)) {
//普通字符串
add(tpl.slice(cursor, match.index));
//js
add(match[1], true); // <-- say that this is actually valid js
cursor = match.index + match[0].length;
}
add(tpl.substr(cursor, tpl.length - cursor));
code += 'return r.join("");'; // <-- return the result
var render = new Function(code.replace(/[\r\t\n]/g, ''));
//有内容直接render返回编译好的字符串
if (data) {
return render.apply(data);
}
//没有内容就返回一个函数可以接受一个参数
var template = function (data) {
return render.call(data);
};
return template;
};
测试一下
var body = $(document.body);
var template = '<p>Hello, my name is <%this.name%>. I\'m <%this.profile.age%> years old.I am good at <%for(var index in this.skills){%> <%this.skills[index]%> <%}%> </p>';
var func = _compile(template);
body.append(func({
name: "BQ",
profile: { age: 21 },
skills: ["js", "html", "css"]
}));
最后渲染出来的如下
Hello, my name is BQ. I'm 21 years old.I am good at js html css
加入缓存机制
var cache = {};
var bTemplate = function (str, data) {
var node = document.getElementById(str);
//传入的是一个#id
if (node) {
if (cache[str]) {
return cache[str];
};
//textarea或input则取value,其它情况取innerHTML
var html = /^(textarea|input)$/i.test(node.nodeName) ? node.value : node.innerHTML;
return _compile(html);
} else {
//是模板字符串,则生成一个函数
//如果直接传入字符串作为模板,则可能变化过多,因此不考虑缓存
return _compile(str);
}
}
这样就高效了一些 但是我仍然没有考虑容错机制 我没想过出错。还应该把所有的函数以及变量放到一个作用域下
我完了应该思考如何实现自定义标签处理