javascript模板系统 ejs v3
本版本增添了局部模板功能,并且允许主模板调用局部模板,局部模块调用局部模块,并去掉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版本加入重新加入@标识符的。