将data和js+html片段解析成html片段,完成赋值和js逻辑,如下所示:
输入:
var data = { name: 'hwm', articles: [{ title: 'the first one' }, { title: 'the last one' }] }; tpl = '<p>Hi my name is <%this.name%>.</p><p>My articles are listed as followed:</p>' + '<ul>' + '<%for(var i = 0; i < this.articles.length; i++) {%>' + '<li><%this.articles[i].title%></li>' + '<%}%>' + '</ul>';
(1)将js片段、赋值片段、html片段解析出来,主要依赖于正则表达式
// 匹配被标识的片段 var reg = /<%(.*?(?=%>))%>/g; // 匹配if、for等开头的逻辑代码 var logicalReg = /^(( )?(if|for|while|else|{|}|switch|case|break))(.*)?/g;
(2)解析结果均是字符串,通过new Function()的方法,可以将字符串传入函数,当做函数体然后执行
于是在字符串的逻辑就是构建一个数组,然后将html和赋值片段push到数组,将逻辑片段添加到字符串,然后通过call将data传入函数体并执行;
// 匹配被标识的片段 var reg = /<%(.*?(?=%>))%>/g; // 匹配if、for等开头的逻辑代码 var logicalReg = /^(( )?(if|for|while|else|{|}|switch|case|break))(.*)?/g; var match; var result = 'var result = [];\n'; var index = 0; // 使用exec方法,得到匹配内容和匹配结果 while (match = reg.exec(tpl)) { addCode(tpl.slice(index, match.index)); addCode(match[1], true); index = match.index + match[0].length; } addCode(tpl.slice(index, tpl.length)); result += 'return result.join("")'; result = result.replace(/[\t\n\r]/g, ''); return new Function(result); function addCode(code, isJs) { // 保证赋值代码不被引号包裹,js逻辑代码直接拼在字符串中 isJs ? result += (code.match(logicalReg) ? code + '\n' : 'result.push(' + code + ');\n') : result += 'result.push("' + code.replace(/"/g, '\\"') + '");\n'; }
(3)建立缓存,当数据变化,模板没有变化时,直接从缓存中调用即可,无需重复解析;
(function () { var cache = {}; this.tmpl = function (id, tpl, data) { var parser = cache[id] || function () {
// 解析代码,同上
}(); cache[id] = parser; return parser.call(data); }; })();
备注:主要参考文章链接如下
http://ejohn.org/blog/javascript-micro-templating/
http://krasimirtsonev.com/blog/article/Javascript-template-engine-in-just-20-line