很早之前就接触过javascript的模板引擎,今天看了这篇文章后深受启发:初识前端模板 ,作者对目前流行的前端引擎作了深入的对比。基于性能和扩展性,兼容性的结果,我决定先对ace-temlate这个模板引擎进行一下演练。
先看一下前面作者的分析比较图:
先看下AceTemplate的源码:
var AceTemplate = AceTemplate || {};
(function(){
/**
* Ace Engine Template
* 一套基于HTML和JS语法自由穿插的模板系统
* http://code.google.com/p/ace-engine/wiki/AceTemplate
* @author 王集鹄(wangjihu,http://weibo.com/zswang) 鲁亚然(luyaran,http://weibo.com/zinkey)
* @version 2011-07-06
* @copyright (c) 2011, Baidu Inc, All rights reserved.
*/
/* Debug Start */
var logger = {
/**
* 打印日志
* @param {Object} text 日志文本
*/
log: function(text) {
/*
var dom = document.getElementById("log");
if (dom) {
dom.value += text + "\n";
}
*/
window.console && console.log(text)
}
};
/* Debug End */
var htmlDecodeDict = { "quot": '"', "lt": "<", "gt": ">", "amp": "&", "nbsp": " " };
var htmlEncodeDict = { '"': "quot", "<": "lt", ">": "gt", "&": "amp", " ": "nbsp" };
var lib = {
/**
* 通过id获得DOM对象
* @param {String} id
*/
g: function(id){
if (typeof id != "string")
return id;
return document.getElementById(id);
},
/**
* HTML解码
* @param {String} html
*/
decodeHTML: function(html) {
return String(html).replace(/&(quot|lt|gt|amp|nbsp);/ig, function(all, key) {
return htmlDecodeDict[key];
}).replace(/&#u([a-f\d]{4});/ig, function(all, hex) {
return String.fromCharCode(parseInt("0x" + hex));
}).replace(/&#(\d+);/ig, function(all, number) {
return String.fromCharCode(+number);
});
},
/**
* HTML编码
* @param {String} html
*/
encodeHTML: function(html) {
return String(html).replace(/["<>& ]/g, function(all) {
return "&" + htmlEncodeDict[all] + ";";
});
},
/**
* 获得元素文本
* @param {Element} element
*/
elementText: function(element) {
if (!element || !element.tagName) return "";
if (/^(input|textarea)$/i.test(element.tagName))
return element.value;
return lib.decodeHTML(element.innerHTML);
}
};
/**
* 解析器缓存
*/
var readerCaches = {};
/**
* 是否注册了所有模板
*/
var registerAll = false;
/**
* 构造模板的处理函数
* 不是JS块的规则
* 非主流字符开头
* 示例:汉字、#{value}、<div>
* 正则:/^\s*[<>!#^&\u0000-\u0008\u007F-\uffff].*$/mg
* html标记结束,如:
* 示例:>、src="1.gif" />
* 正则:/^.*[<>]\s*$/mg
* 没有“双引号、单引号、分号、逗号,大小括号”,不是else等单行语句、如:
* 示例:hello world
* 正则:/^(?!\s*(else|do|try|finally)\s*$)[^'":;,\[\]{}()\n\/]+$/mg
* 属性表达式
* 示例:a="12" b="45"、a='ab' b="cd"
* 正则:/^(\s*(([\w-]+\s*=\s*"[^"]*")|([\w-]+\s*=\s*'[^']*')))+\s*$/mg
* 样式表达式
* 示例:div.focus{color: #fff;}、#btnAdd span{}
* 正则:/^\s*([.#][\w-.]+(:\w+)?(\s*|,))*(?!(else|do|while|try|return)\b)[.#]?[\w-.*]+(:\w+)?\s*\{.*$/mg
* @param {String} template 模板字符
*/
function analyse(template) {
var body = [], processItem = [];
body.push("with(this){");
body.push(template
.replace(/[\r\n]+/g, "\n") // 去掉多余的换行,并且去掉IE中困扰人的\r
.replace(/^\n+|\s+$/mg, "") // 去掉空行,首部空行,尾部空白
.replace(/((^\s*[<>!#^&\u0000-\u0008\u007F-\uffff].*$|^.*[<>]\s*$|^(?!\s*(else|do|try|finally)\s*$)[^'":;,\[\]{}()\n\/]+$|^(\s*(([\w-]+\s*=\s*"[^"]*")|([\w-]+\s*=\s*'[^']*')))+\s*$|^\s*([.#][\w-.]+(:\w+)?(\s*|,))*(?!(else|do|while|try|return)\b)[.#]?[\w-.*]+(:\w+)?\s*\{.*$)\s?)+/mg, function(expression) { // 输出原文
expression = ['"', expression
.replace(/&none;/g, "") // 空字符
.replace(/["'\\]/g, "\\$&") // 处理转义符
.replace(/\n/g, "\\n") // 处理回车转义符
.replace(/(!?#)\{(.*?)\}/g, function (all, flag, template) { // 变量替换
template = template.replace(/\\n/g, "\n").replace(/\\([\\'"])/g, "$1"); // 还原转义
var identifier = /^[a-z$][\w+$]+$/i.test(template) &&
!(/^(true|false|NaN|null|this)$/.test(template)); // 单纯变量,加一个未定义保护
return ['",',
identifier ? ['typeof ', template, '=="undefined"?"":'].join("") : "",
(flag == "#" ? '_encode_' : ""),
'(', template, '),"'].join("");
}), '"'].join("").replace(/^"",|,""$/g, "");
if (expression)
return ['_output_.push(', expression, ');'].join("");
else return "";
}));
body.push("}");
var result = new Function("_output_", "_encode_", "helper", body.join(""));
/* Debug Start */
logger.log(String(result));
/* Debug End */
return result;
}
/**
* 格式化输出
* @param {String|Element} id 模板ID或是模板本身(非标识符将识别为模板本身)
* @param {Object} data 格式化的数据,默认为空字符串
* @param {Object} helper 附加数据(默认为模板对象)
*/
AceTemplate.format = function(id, data, helper) {
if (!id) return "";
var reader, element;
if (typeof id == "object" && id.tagName) { // 如果是Dom对象
element = id;
id = element.getAttribute("id");
}
helper = helper || this; // 默认附加数据
reader = readerCaches[id]; // 优先读取缓存
if (!reader) { // 缓存中未出现
if (!/[^\w-]/.test(id)) { // 合法的标识符按id读取
if (!element) {
element = lib.g(id);
}
reader = this.register(id, element);
} else {
reader = analyse(id);
}
}
var output = [];
reader.call(data || "", output, lib.encodeHTML, helper);
return output.join("");
};
/**
* 注册模板,如果没有参数则是注册所有script标签模板
* @param {String} id 模板ID
* @param {Element|String} target 模板对象或者是模板字符串,如果没有则默认获取id对应的DOM对象
*/
AceTemplate.register = function(id, target) {
if (!arguments.length && !registerAll) { // 无参数并且没有注册过
registerAll = true;
var scripts = document.getElementsByTagName("script");
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
if (/^(text\/template)$/i.test(script.getAttribute("type"))) {
var id = script.getAttribute("id");
id && arguments.callee.call(this, id, script);
}
}
}
if (!id) return;
if (readerCaches[id]) { // 如果已经注册
return readerCaches[id];
}
if (typeof target != "string") {
if (typeof target == "undefined") {
target = lib.g(id);
}
target = lib.elementText(target);
}
return readerCaches[id] = analyse(target);
};
/**
* 注销模板
* @param {String} id 模板ID
*/
AceTemplate.unregister = function(id) {
delete readerCaches[id];
};
})();
1.使用Jquery输出列表
Html模板部分
<script id="t1" type="text/template">
for (var i = 0; i < this.length; i++) {
var item = this[i];
<li>
<b>第#{i + 1}名</b><a href="#{item.url}" target="blank">#{item.title}</a>
if (item.isnew) {
<span>new!</span>
}
</li>
}
</script>
<div>
<ul id="t1out">
</ul>
</div>
Js部分
<script type="text/javascript">
$(function () {
var movieList = [
{
title: "建党伟业",
url: "#"
},
{
title: "变形金刚3",
url: "#",
isnew: true
},
{
title: "功夫熊猫2",
url: "#"
},
{
title: "加勒比海盗4",
url: "#"
},
{
title: "3d肉蒲团",
url: "#"
}
];
$("#t1out").html(AceTemplate.format("t1",movieList));
});
</script>
熟悉MVC的朋友是不是一眼就看出来,对,这就是JS的MVC模板引擎。
是不是很简单,我们来总结一下循环输出的语法规则:
1.使用Javascript原始的循环嵌套HTML代码定义
for (var i = 0; i < this.length; i++) { var item = this[i]; <li> <b>第#{i + 1}名</b><a href="#{item.url}" target="blank">#{item.title}</a> if (item.isnew) { <span>new!</span> } </li>
2.使用Jquery的语法规则嵌套HTML定义
$.each(this, function(i, item) { <li> <b>第#{i + 1}名</b><a href="#{item.url}" target="blank">#{item.title}</a> if (item.isnew) { <span>new!</span> } </li>
DEMO下载:下载地址 (猛击它就可以下载了)