本版本增添了局部模板功能,并且允许主模板调用局部模板,局部模块调用局部模块,并去掉onsite变量,不再提供解析成毕的文档碎片给使用者。它使用双重缓存,一是缓存那些通过同步请求得到的文本而成的数组,一是整体解析完毕得到的模板函数。模板函数是通过数组元素拼凑动成解析而成的,这是大大提高了效率。不过由于新功能的加入,虽然动用了新的构筑算法也比不上v2的构筑速度了……
有人说不要使用<%与%>做界定符,这个问题我在v1版本已经提出过了,这些都是可以自定义的。本文的例子将演示一下如何使用Django的{{与}}风格。
最后隆重推介一下本版本的新功能。不过由于条件有限,无法演示。现在模板不单单是内嵌于页面的script标签之内,也可以放置在一个独立的文件之内,如html,ejs,text,随你起什么后缀名。这个文件将会用同步请求回来用于构筑模板函数。我们可以用使用url属性,或在模板中使用<%: /template/partail.ejs >实现。一般而言,url是用于主模板,而<%: url %>是用于局部模板。如果我们在配置对象中同时使用selector与url,selector的优先级是高于url的。
var data = dom.ejs({
selector:"tmpl",
url:"/template/aaa.html",
left:"{{",
right:"}}",
json: {
name:"司徒正美",
blog:"ruby louvre"
address:"异次元"
}
});
源码:
//司徒正美 javascript template - http://www.cnblogs.com/rubylouvre/ - MIT Licensed
(function () {
if(!String.prototype.trim){
String.prototype.trim = function(){
return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
}
}
var dom = {
quote: function (str) {
str = str.replace(/[\x00-\x1f\\]/g, function (chr) {
var special = metaObject[chr];
return special ? special : '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4)
});
return '"' + str.replace(/"/g, '\\"') + '"';
}
},
metaObject = {
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'\\': '\\\\'
},
startOfHTML = "\t__views.push(",
endOfHTML = ");\n";
(function(){
//http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
var s = ["XMLHttpRequest",
"ActiveXObject('Msxml2.XMLHTTP.6.0')",
"ActiveXObject('Msxml2.XMLHTTP.3.0')",
"ActiveXObject('Msxml2.XMLHTTP')",
"ActiveXObject('Microsoft.XMLHTTP')"];
if( eval("''+/*@cc_on"+" @_jscript_version@*/-0")*1 === 5.7 && location.protocol === "file:"){
s.shift();
}
for(var i = 0 ,el;el=s[i++];){
try{
if(eval("new "+el)){
dom.xhr = new Function( "return new "+el)
break;
}
}catch(e){}
}
})();
dom.partial = function(url){
var xhr = dom.xhr();
xhr.open("GET",url,false);
xhr.setRequestHeader("If-Modified-Since","0");
xhr.send(null);
return xhr.responseText|| ""
}
dom.tmpl = function(str,rLeft,rRight,sRight){
var arr = str.trim().split(rLeft),self = arguments.callee,buff = [],url,els,el,i = 0, n= arr.length;
while (i<n) {
els = arr[i++]; el = els.split(rRight);
if(els.indexOf(sRight) !== -1){//这里不使用els.length === 2是为了避开IE的split bug
switch (el[0].charAt(0)) {
case "#"://处理注释
break;
case "="://处理后台返回的变量(输出到页面的);
buff.push(startOfHTML, el[0].substring(1), endOfHTML)
break;
case ":"://处理局部模板
url = el[0].substring(1).trim();
//缓存构筑函数的数组
self[url] = self[url] || self.call(null,dom.partial(url),rLeft,rRight,sRight);
buff = buff.concat(dom.tmpl[url] );
break;
default:
buff.push(el[0], "\n");
};
el[1] && buff.push(startOfHTML, dom.quote.call(null,el[1]), endOfHTML);
}else{
buff.push(startOfHTML, dom.quote.call(null,el[0]), endOfHTML);
}
}
return buff;
}
dom.ejs = function (obj) {
var sLeft = obj.left || "%>",
sRight = obj.right || "<%",
rLeft = new RegExp("\\s*"+sLeft+"\\s*"),
rRight = new RegExp("\\s*"+sRight+"\\s*"),
buff = ["var __views = [];\n"],
key = obj.selector || obj.url,str;
if(obj.selector){
var el = document.getElementById(key);
if (!el) throw "找不到目标元素";
str = el.text;
}else{
str = dom.partial(key);
if(!str) throw "目标文件不存在";
}
if(!dom.tmpl[key]){//缓存模板函数
buff = buff.concat(dom.tmpl.call(null,str,rLeft,rRight,sRight));
dom.tmpl[key] = new Function("json", "with(json){"+buff.join("") + '\t};return __views.join("");');
}
return dom.tmpl[key](obj.json || {});
};
window.dom = dom;
})();
示例:
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta content="IE=8" http-equiv="X-UA-Compatible"/>
<meta name="keywords" content="javascript模板 by 司徒正美" />
<meta name="description" content="javascript模板 by 司徒正美" />
<title>javascript模板 by 司徒正美</title>
</head>
<body>
<h1>javascript模板 by 司徒正美</h1>
<div id="tmplTC">这是容器</div>
<script id="tmpl" type="tmpl">
<h2>{{= name }}{{= name }}</h2>
{{# 这是注释!!!!!!!!! }}
<ul>
{{ for(var i=0; i< uls.length; i++){ }}
<li>{{= uls[i] }}的名字是{{= name }}</li>
{{ } }}
</ul>
{{ var color = "color:red;" }}
<p style="text-indent:2em;{{= color }} ">{{= address }}</p>
</script>
<script src="dom/ejs.js"></script>
<script>
window.onload = function(){
var els = [];
for(var i=0;i<1000;i++){
els.push("第"+i+"个元素")
}
var a = new Date
var data = dom.ejs({
selector:"tmpl",
left:"{{",
right:"}}",
json: {
name:"司徒正美",
uls:els,
address:"异次元"
}
});
document.getElementById("tmplTC").innerHTML = data;
alert( new Date-a)
}
</script>
</body>
</html>
现在我有一个考量,就是随着模板规模的膨胀,里面可能夹杂着越来越多变量,我们就很难分辨得清那些后台传过的东西,那些是本地的临时变量,后台的需求一变更,后台的json数据也就要改动。这维护起来非常困难。因此我非常欣赏ruby的变量书写风格,从变量名就知它是实例变量,类变量,普通变量与常量,它还有符号这东西呢,我会v4版本加入重新加入@标识符的。