本版本增添了局部模板功能,並且允許主模板調用局部模板,局部模組調用局部模組,並去掉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>
<br /><!doctype html><br /><html><br /> <head><br /> <meta charset="utf-8"/><br /> <meta content="IE=8" http-equiv="X-UA-Compatible"/><br /> <meta name="keywords" content="javascript模板 by 司徒正美" /><br /> <meta name="description" content="javascript模板 by 司徒正美" /><br /> <title>javascript模板 by 司徒正美</title><br /> </head><br /> <body><br /> <h1>javascript模板 by 司徒正美</h1><br /> <div id="tmplTC">這是容器</div><br /> <script id="tmpl" type="tmpl"><br /> <h2>{{= name }}{{= name }}</h2><br /> {{# 這是注釋!!!!!!!!! }}<br /> <ul><br /> {{ for(var i=0; i< uls.length; i++){ }}<br /> <li>{{= uls[i] }}的名字是{{= name }}</li><br /> {{ } }}<br /> </ul><br /> {{ var color = "color:red;" }}<br /> <p style="text-indent:2em;{{= color }} ">{{= address }}</p></p><p> </script></p><p> <script><br /> (function () {</p><p> if(!String.prototype.trim){<br /> String.prototype.trim = function(str){<br /> return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');<br /> }<br /> }<br /> var dom = {<br /> quote: function (str) {<br /> str = str.replace(/[\x00-\x1f\\]/g, function (chr) {<br /> var special = metaObject[chr];<br /> return special ? special : '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4)<br /> });<br /> return '"' + str.replace(/"/g, '\\"') + '"';<br /> }<br /> },<br /> metaObject = {<br /> '\b': '\\b',<br /> '\t': '\\t',<br /> '\n': '\\n',<br /> '\f': '\\f',<br /> '\r': '\\r',<br /> '\\': '\\\\'<br /> },<br /> startOfHTML = "\t__views.push(",<br /> endOfHTML = ");\n";<br /> (function(){<br /> //http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx<br /> var s = ["XMLHttpRequest",<br /> "ActiveXObject('Msxml2.XMLHTTP.6.0')",<br /> "ActiveXObject('Msxml2.XMLHTTP.3.0')",<br /> "ActiveXObject('Msxml2.XMLHTTP')",<br /> "ActiveXObject('Microsoft.XMLHTTP')"];<br /> if( eval("''+/*@cc_on"+" @_jscript_version@*/-0")*1 === 5.7 && location.protocol === "file:"){<br /> s.shift();<br /> }<br /> for(var i = 0 ,el;el=s[i++];){<br /> try{<br /> if(eval("new "+el)){<br /> dom.xhr = new Function( "return new "+el)<br /> break;<br /> }<br /> }catch(e){}<br /> }<br /> })();</p><p> dom.partial = function(url){<br /> var xhr = dom.xhr();<br /> xhr.open("GET",url,false);<br /> xhr.setRequestHeader("If-Modified-Since","0");<br /> xhr.send(null);<br /> return xhr.responseText|| ""<br /> }</p><p> dom.tmpl = function(str,rLeft,rRight,sRight){<br /> var arr = str.trim().split(rLeft),self = arguments.callee,buff = [],url,els,el,i = 0, n= arr.length;<br /> while (i<n) {<br /> els = arr[i++]; el = els.split(rRight);<br /> if(els.indexOf(sRight) !== -1){//這裡不使用els.length === 2是為了避開IE的split bug<br /> switch (el[0].charAt(0)) {<br /> case "#"://處理注釋<br /> break;<br /> case "="://處理後台返回的變數(輸出到頁面的);<br /> buff.push(startOfHTML, el[0].substring(1), endOfHTML)<br /> break;<br /> case ":"://處理局部模板<br /> url = el[0].substring(1).trim();<br /> self[url] = self[url] || self.call(null,dom.partial(url),rLeft,rRight,sRight);<br /> buff = buff.concat(dom.tmpl[url] );<br /> break;<br /> default:<br /> buff.push(el[0], "\n");<br /> };<br /> el[1] && buff.push(startOfHTML, dom.quote.call(null,el[1]), endOfHTML);<br /> }else{<br /> buff.push(startOfHTML, dom.quote.call(null,el[0]), endOfHTML);<br /> }<br /> }<br /> return buff;<br /> }</p><p> dom.ejs = function (obj) {<br /> var sLeft = obj.left || "%>",<br /> sRight = obj.right || "<%",<br /> rLeft = new RegExp("\\s*"+sLeft+"\\s*"),<br /> rRight = new RegExp("\\s*"+sRight+"\\s*"),<br /> buff = ["var __views = [];\n"],<br /> key = obj.selector || obj.url,str;<br /> if(obj.selector){<br /> var el = document.getElementById(key);<br /> if (!el) throw "找不到目標元素";<br /> str = el.text;<br /> }else{<br /> str = dom.partial(key);<br /> if(!str) throw "目標檔案不存在";<br /> }<br /> if(!dom.tmpl[key]){//以選取器-->解析函數的形式緩衝到dom.tmpl函數中<br /> buff = buff.concat(dom.tmpl.call(null,str,rLeft,rRight,sRight));<br /> dom.tmpl[key] = new Function("json", "with(json){"+buff.join("") + '\t};return __views.join("");');<br /> }<br /> return dom.tmpl[key](obj.json || {});<br /> };<br /> window.dom = dom;</p><p> })();</p><p> window.onload = function(){<br /> var els = [];<br /> for(var i=0;i<1000;i++){<br /> els.push("第"+i+"個元素")<br /> }<br /> var a = new Date<br /> var data = dom.ejs({<br /> selector:"tmpl",<br /> left:"{{",<br /> right:"}}",<br /> json: {<br /> name:"司徒正美",<br /> uls:els,<br /> address:"異次元"<br /> }<br /> });<br /> document.getElementById("tmplTC").innerHTML = data;<br /> alert( new Date-a)<br /> }<br /> </script><br /> </body><br /></html><br />
運行代碼
現在我有一個考量,就是隨著模板規模的膨脹,裡面可能夾雜著越來越多變數,我們就很難分辨得清那些後台傳過的東西,那些是本地的臨時變數,背景需求一變更,背景json資料也就要改動。這維護起來非常困難。因此我非常欣賞ruby的變數書寫風格,從變數名就知它是執行個體變數,類變數,普通變數與常量,它還有符號這東西呢,我會v4版本加入重新加入@標識符的。