前端JavaScript模板引擎

Heero.Luo发表于13年前,已被查看5194次

说起模板,很多人会认为这是后台的东西(如PHP的Smarty、Java的Velocity),跟前端没有关系。然而,随着前端的逻辑变得越来越复杂,引入模板引擎已经是非常必要了。

模板引擎的主要功能就是把变化的数据融入到不变的模板中,并生成最终结果。目前,前端的主要数据格式无非是XMLJSON

如果选择XML作为数据格式,XSLT就是最佳的模板语言,但XML+XSLT的缺点非常明显:

  • 兼容性问题。XML+XSLT在不同浏览器下的转换方式有所不同。
  • XML、XSLT的语法都是极其冗余的,数据量相对较大。

如果选择JSON作为数据格式,就没有原生的模板语言可用了,只能生拼字符串。例如,把下面代码中的data转换成一个表格:

var data = [
	{ id : 1, name : 'Google', url : "google.com" },
	{ id : 2, name : '百度', url : "baidu.com" },
	{ id : 3, name : '有道', url : "youdao.com" }
], html = [ ];

html.push('<table>');
for (var i = 0; i < data.length; i++) {
	html.push('<tr>');
	html.push('<td>', data[i].id, '</td>');
	html.push('<td><a href="http://', data[i].url, '" target="_blank">', data[i].name, '</a></td>');
	html.push('</tr>');
}
html.push('</table>');

document.write(html.join('\r\n'));

看到这么多单引号、双引号之后,我觉得很头晕。这明显是不可取的。恰巧,大三下学期实习的时候,在一位前辈的代码中看到一种正则表达式做的简单模板引擎

function parseTemplate(data, tpl) {
	return tpl.replace(/\{\$(\w+)\}/g, function($0, $1) {
		return data[$1];
	});
}

有了parseTemplate后,连接字符串的过程就可以简化为(粗体部分为模板):

html.push('<table>');
for (var i = 0; i < data.length; i++) {
	html.push(
		parseTemplate(data[i], '<tr><td>{$id}</td><td><a href="{$url}" target="_blank">{$name}</a></td></tr>')
	);
}
html.push('</table>');

这样优化过后,代码清晰了一点。然而,这种模板引擎无法应付稍微复杂一点的逻辑。假设我要突出显示排在第一位的数据,parseTemplate就无能为力了。

模板引擎必须要支持for、if等简单逻辑,否则其应用范围就会非常小。然而,要支持这些逻辑,又必须要有一套完善的语法系统。后来,还是著名的JavaScript大神John Resig给我们解决了这个问题。他写了一个简短的函数实现了支持JavaScript语法的模板引擎(原版见此处,下面是本人修改过的简化版):

var tmpl = (function() {
	var cache = { };

	return function(str, data) {
		var fn = cache[str];
		if (!fn) {
			fn = new Function("obj",
				"var p=[];" +
				"with(obj){p.push('" +
				str
					.replace(/[\r\t\n]/g, " ")
					.split("<%").join("\t")
					.replace(/((^|%>)[^\t]*)'/g, "$1\r")
					.replace(/\t=(.*?)%>/g, "',$1,'")
					.split("\t").join("');")
					.split("%>").join("p.push('")
					.split("\r").join("\\'")
				+ "');}return p.join('');"
			);
			cache[str] = fn;
		}

		return fn(data);
  	};
})();

其原理是把模板“编译”为JavaScript的原生语法。这个引擎的优点在于:

  • 可以使用任何JavaScript支持的语法。
  • 渲染的过程在闭包内执行,不会产生多余的全局变量。
  • 对“编译”后的模板进行了缓存,下次可以跳过“编译”的过程直接使用。

利用这个模板引擎,上面的代码可以再简化为:

var template = '\
	<table>\
	<% for (var i = 0; i < data.length; i++) { %>\
	<tr>\
	<td><%=data[i].id%></td>\
	<td><a href="<%=data[i].url%>" target="_blank"><%=data[i].name%></a></td>\
	</tr>\
	<% } %>\
	\
	</table>';

document.write( tmpl(template, { data : data }) );

至此,显示的逻辑已经完全在模板里面,代码清晰明了。

评论 (7条)

发表评论

(必填)

(选填,不公开)

(选填,不公开)

(必填)