javascript模板系统 ejs v8

ejsv8对ejsv7进行大量简化与提速工作,本版本进行了以下改进。

  • 去掉去掉参数多态化,现在只有两个参数。第一个参数为script标签的ID,第二个参数对数据对象
  • 去掉@标识符,网友反映这东西很怪
  • 去掉远程模板支持,因为怎么远程也一定要同域才行,要不AJAX获取不到,鸡肋。以后模板统一写到type为"text/html"的scrpt元素中。
  • 优化quote函数。网上有许多JS模板都是直接用正则进行全文转义,但怎么说也不比上quote函数安全。
  • 使用apply对传参进行优化。indexOf判定优化。
  • 代码量由140行缩减到75行。
      //dom.ejs v8 by 司徒正美
      //http://www.cnblogs.com/rubylouvre/archive/2011/03/03/1969718.html
      ;
      (function(DOC){
        this.dom = {
          quote :  window.JSON && JSON.stringify || String.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, '\\"') + '"';
          }

        }
        if(!String.prototype.trim){
          String.prototype.trim = function(){
            return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
          }
        }
        var
        metaObject = {
          '\b': '\\b',
          '\t': '\\t',
          '\n': '\\n',
          '\f': '\\f',
          '\r': '\\r',
          '\\': '\\\\'
        },
        _startOfHTML = "\t__views.push(",
        _endOfHTML = ");\n",
        sRight = "&>",
        rLeft = /\s*<&\s*/,
        rRight = /\s*&>\s*/
        var ejs2 = dom.ejs = function(id,data){
          if(!ejs2[id]){
            var rleft = rLeft, rright = rRight, sright = sRight,startOfHTML = _startOfHTML, endOfHTML = _endOfHTML, str , logic,
            el = DOC.getElementById(id);
            if (!el) throw "can not find the target element";
            str = el.text;
            var arr = str.trim().split(rleft),
            temp = ["var __views = [];\n"],i = 0,n = arr.length,els,segment;
            while(i < n){//逐行分析,以防歧义
              segment = arr[i++];
              els = segment.split(rright);
              if(~segment.indexOf(sright) ){//这里不使用els.length === 2是为了避开IE的split bug
                switch (els[0].charAt(0)) {
                  case "="://处理后台返回的变量(输出到页面的);
                    logic = els[0].substring(1);
                    temp.push(startOfHTML, logic, endOfHTML);
                    break;
                  case "#"://处理注释
                    break;
                  default://处理逻辑
                    logic = els[0];
                    temp.push(logic, "\n");
                }
                //处理静态HTML片断
                els[1] &&  temp.push(startOfHTML,dom.quote.call(null,els[1]), endOfHTML);//转义
              }else{
                //处理静态HTML片断
                temp.push(startOfHTML, dom.quote.call(null,els[0]),endOfHTML);
              }
            }
            var keys = [], values = [];
            for( i in data){
              keys.push(i);
              values.push(data[i]);
            }
            keys.push(temp.concat(" return __views.join('');").join(""))
            return (ejs2[id] = Function.apply(0,keys)).apply(0,values);
          }
          var vals = []
          for( i in data){
            vals.push(data[i]);
          }
          return ejs2[id].apply(0,vals);
        }
      })(document);

用法var str = dom.ejs(id,data)。第一个参数为script标签的ID,第二个参数对数据对象。str为处理好的HTML片断字符,直接el.innerHTML = str就行。界定符依旧是<&与>&,如果<紧跟着的是=号,说明这后面的部要输出页面,若是#号,则是注释,不输出页面,其他部分与ASP的使用方法一样。

<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta content="IE=8" http-equiv="X-UA-Compatible"/> <title>javascript 模板系统 ejs v8 by 司徒正美</title> <style type="text/css"> table { border:1px solid #a9ea00; border-collapse:collapse; width:80%; } td{ border:1px solid #a9ea00; padding:0; text-align:center; } </style> <script> //dom.ejs v8 by 司徒正美 //http://www.cnblogs.com/rubylouvre/archive/2011/03/03/1969718.html ; (function(DOC){ this.dom = { quote : String.quote || window.JSON && (typeof JSON.stringify === "function") && JSON.stringify ||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, '\\"') + '"'; } } if(!String.prototype.trim){ String.prototype.trim = function(){ return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); } } var metaObject = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' }, _startOfHTML = "\t__views.push(", _endOfHTML = ");\n", sRight = "&>", rLeft = /\s*<&\s*/, rRight = /\s*&>\s*/ var ejs2 = dom.ejs = function(id,data){ if(!ejs2[id]){ var rleft = rLeft, rright = rRight, sright = sRight,startOfHTML = _startOfHTML, endOfHTML = _endOfHTML, str , logic, el = DOC.getElementById(id); if (!el) throw "can not find the target element"; str = el.text; var arr = str.trim().split(rleft),// 转义分解 temp = ["var __views = [];\n"],i = 0,n = arr.length,els,segment; while(i < n){//逐行分析,以防歧义 segment = arr[i++]; els = segment.split(rright); if(~segment.indexOf(sright) ){//这里不使用els.length === 2是为了避开IE的split bug switch (els[0].charAt(0)) { case "="://处理后台返回的变量(输出到页面的); logic = els[0].substring(1); temp.push(startOfHTML, logic, endOfHTML); break; case "#"://处理注释 break; default://处理逻辑 logic = els[0]; temp.push(logic, "\n"); } //处理静态HTML片断 els[1] && temp.push(startOfHTML,dom.quote.call(null,els[1]), endOfHTML) }else{ //处理静态HTML片断 temp.push(startOfHTML, dom.quote.call(null,els[0]),endOfHTML) } } var keys = [], values = []; for( i in data){ keys.push(i); values.push(data[i]); } keys.push(temp.concat(" return __views.join('');").join("")) return (ejs2[id] = Function.apply(0,keys)).apply(0,values); } var vals = [] for( i in data){ vals.push(data[i]); } return ejs2[id].apply(0,vals); } })(document); </script> <script> window.onload = function(){ var trs = [ {name:"隐形杀手",age:29,sex:"男"}, {name:"索拉",age:22,sex:"男"}, {name:"fesyo",age:23,sex:"女"}, {name:"恋妖壶",age:18,sex:"男"}, {name:"竜崎",age:25,sex:"男"}, {name:"你不懂的",age:30,sex:"女"} ] var lis = { aaa:"AAA", bbb:"BBB", ccc:"CCC" } var tmpl = dom.ejs("js_table_tmpl",{ title:"dom.ejs v8 ", name:"司徒正美", trs:trs, lis:lis, href:"http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_type4.jpg" }); document.getElementById("js_table_tc").innerHTML = tmpl; } </script> </head> <body> <script type="tmpl" id="js_table_tmpl"> <h2><&= title &> by <&= name &></h2> <table> <& for(var i=0,tl = trs.length,tr;i < tl;i++){ &> <& tr = trs[i]; &> <tr> <td><&= tr.name &></td> <td><&= tr.age &></td> <td><&= tr.sex || "男" &></td> <&= dom.ejs("js_tds_tmpl",{}) &> </tr> <& } &> </table> < 怎么可能不支持图片 &> <img src="<&= href &>"> <ol><&= dom.ejs("js_lis_tmpl",lis) &></ol> </script> <script type="tmpl" id="js_lis_tmpl"> <li><&= aaa &></li> <li><&= bbb &></li> <li><&= ccc &></li> </script> <script type="tmpl" id="js_tds_tmpl"> <td>静态的表格</d> <td>静态的表格</d> <td>静态的表格</d> </script> <div id="js_table_tc"> </div> </body> </html>

运行代码

下面是与糖饼的tmplv2,公认最快的模板YayaTemplate,ejsv7的速度比较,为了公平起见,ejsv7,v8都去掉缓存功能。

<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>速度比赛</title> <script> //dom.ejs v8 by 司徒正美 //http://www.cnblogs.com/rubylouvre/archive/2010/10/04/1841933.html ; (function(DOC){ this.neo = {} if(!String.prototype.trim){ String.prototype.trim = function(){ return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); } } var _startOfHTML = "\t__views.push(", _endOfHTML = ");\n", sRight = "&>", rLeft = /\s*<&\s*/, rRight = /\s*&>\s*/ neo.ejs = function(id,data){ var rleft = rLeft, rright = rRight, sright = sRight,startOfHTML = _startOfHTML, endOfHTML = _endOfHTML, str , logic, el = DOC.getElementById(id); if (!el) throw "can not find the target element"; str = el.text; var arr = str.trim().replace(/("|\\)/g,"\\$1").split(rleft),// temp = ["var __views = [];\n"],i = 0,n = arr.length,els,segment; while(i < n){ segment = arr[i++]; els = segment.split(rright);//按照 &> if(~segment.indexOf(sright) ){//这里不使用els.length === 2是为了避开IE的split bug switch (els[0].charAt(0)) { case "="://处理后台返回的变量(输出到页面的); logic = els[0].substring(1); temp.push(startOfHTML, logic, endOfHTML); break; case "#"://处理注释 break; default://处理逻辑 logic = els[0]; temp.push(logic, "\n"); } //处理静态HTML片断 els[1] && temp.push(startOfHTML, '"',els[1],'"', endOfHTML) }else{ //处理静态HTML片断 temp.push(startOfHTML, '"',els[1],'"',endOfHTML) } } var keys = [], values = [] for( i in data){ keys.push(i); values.push(data[i]); } keys.push(temp.concat(" return __views.join('');").join("")) return (Function.apply(0,keys)).apply(0,values); } })(document); </script> <script> ; (function(){ this.dom = { quote : String.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, '\\"') + '"'; }, mix : function(target, source ,override) { var i, ride = (override === void 0) || override; for (i in source) { if (ride || !(i in target)) { target[i] = source[i]; } } return target; } } if(!String.prototype.trim){ String.prototype.trim = function(){ return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); } } ; (function(w,s){ //http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx s = ["XMLHttpRequest", "ActiveXObject('Msxml2.XMLHTTP.6.0')", "ActiveXObject('Msxml2.XMLHTTP.3.0')", "ActiveXObject('Msxml2.XMLHTTP')", "ActiveXObject('Microsoft.XMLHTTP')"]; //IE专有的JScript方法:ScriptEngine,ScriptEngineBuildVersion,ScriptEngineMajorVersion,ScriptEngineMinorVersion,CollectGarbage,RuntimeObject和GetObject。 if( !-[1,] && w.ScriptEngineMinorVersion() === 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){} } })(window); var metaObject = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' }, _startOfHTML = "\t__views.push(", _endOfHTML = ");\n", _rAt = /(^|[^\w\u00c0-\uFFFF_])(@)(?=\w)/g, _partial = function(url){ var xhr = dom.xhr(); xhr.open("GET",url,false); xhr.setRequestHeader("If-Modified-Since","0"); xhr.send(null); return xhr.responseText|| "" }, defaults = { left: "<&", right: "&>" } dom.ejs = function(obj,data){ if(typeof obj === "string"){ if(obj.indexOf("url(") === 0){ obj = { url : obj.slice(4,-1), data: data } }else{ obj = { selector:obj, data :data } } } dom.mix(obj, defaults,false); if(!obj.rLeft){ obj.rLeft = new RegExp("\\s*"+obj.left+"\\s*") obj.rRight = new RegExp("\\s*"+ obj.right+"\\s*"); } var key = obj.selector || obj.url; var rAt = _rAt, startOfHTML = _startOfHTML, endOfHTML = _endOfHTML,partial = _partial, buff = ["var __views = [];\n"],str , logic; if(obj.selector){ var el = document.getElementById(key); if (!el) throw "can not find the target element"; str = el.text; }else { str = partial(obj.url); if (!str) throw "the target file does not exist"; } var arr = str.trim().split(obj.rLeft),temp = [],i = 0,n = arr.length,els,segment; while(i < n){ segment = arr[i++]; els = segment.split(obj.rRight); if(segment.indexOf(obj.right) !== -1){//这里不使用el.length === 2是为了避开IE的split bug switch (els[0].charAt(0)) { case "="://处理后台返回的变量(输出到页面的); logic = els[0].substring(1); if(logic.indexOf("@")!==-1){ temp.push(startOfHTML, logic.replace(rAt,"$1data."), endOfHTML); }else{ temp.push(startOfHTML, logic, endOfHTML); } break; case "#"://处理注释 break; default://处理逻辑 logic = els[0]; if(logic.indexOf("@")!==-1){ temp.push(logic.replace(rAt,"$1data."), "\n"); }else{ temp.push(logic, "\n"); } } //处理静态HTML片断 els[1] && temp.push(startOfHTML, dom.quote.call(null,els[1]), endOfHTML) }else{ //处理静态HTML片断 temp.push(startOfHTML, dom.quote.call(null,els[0]), endOfHTML) } } var fn = new Function("data",buff.concat(temp).join("")+';return __views.join("");'); return fn(obj.data || {}); } })(); </script> <script> //YayaTemplate //author:yaya,jihu //uloveit.com.cn/template //how to use? YayaTemplate("xxx").render({}); var YayaTemplate = YayaTemplate || function(str){ //核心分析方法 var _analyze=function(text){ return text.replace(/{\$(\s|\S)*?\$}/g,function(s){ return s.replace(/("|\\)/g,"\\$1") .replace("{$",'_s.push("') .replace("$}",'");') .replace(/{\%([\s\S]*?)\%}/g, '",$1,"') }).replace(/\r|\n/g,""); }; //中间代码 var _temp = _analyze(document.getElementById(str)?document.getElementById(str).innerHTML:str); //返回生成器render方法 return { render : function(mapping){ var _a = [],_v = [],i; for (i in mapping){ _a.push(i); _v.push(mapping[i]); } return (new Function(_a,"var _s=[];"+_temp+" return _s;")).apply(null,_v).join(""); } } }; </script> <script> /** * 微型模板引擎 tmpl 0.2 * 0.2 更新: * 1. 修复转义字符与id判断的BUG * 2. 放弃低效的 with 语句从而最高提升3.5倍的执行效率 * 3. 使用随机内部变量防止与模板变量产生冲突 * @see http://ejohn.org/blog/javascript-micro-templating/ * @param {String} 模板内容或者装有模板内容的元素ID * @param {Object} 附加的数据 * @return {String} 解析好的模板 */ var tmpl = (function (cache, $) { return function (str, data) { var fn = !/\s/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : function (data) { var i, variable = [$], value = [[]]; for (i in data) { variable.push(i); value.push(data[i]); }; return (new Function(variable, fn.$)) .apply(data, value).join(""); }; fn.$ = fn.$ || $ + ".push('" + str.replace(/\\/g, "\\\\") .replace(/[\r\t\n]/g, " ") .split("<%").join("\t") .replace(/((^|%>)[^\t]*)'/g, "$1\r") .replace(/\t=(.*?)%>/g, "',$1,'") .split("\t").join("');") .split("%>").join($ + ".push('") .split("\r").join("\\'") + "');return " + $; return data ? fn(data) : fn; }})({}, '$' + (+ new Date)); </script> <!-- tmpl 的模板 --> <script id="tmpl" type="text/tmpl"> <ul> <% for (var i = 0, l = list.length; i < l; i ++) { %> <li><%=list[i].index%>. 用户: <%=list[i].user%>; 网站:<%=list[i].site%></li> <% } %> </ul> </script> <script id="ejs" type="text/tmpl"> <ul> <& for (var i = 0, l = @list.length; i < l; i ++) { &> <li> <&= @list[i].index &>. 用户: <&= @list[i].user &>; 网站:<&= @list[i].site &></li> <& } &> </ul> </script> <!-- YayaTemplate 的模板 --> <script id="yaya" type="text/tmpl"> {$<ul>$} for (var i = 0, l = list.length; i < l; i ++) { {$ <li>{%list[i].index%}. 用户: {%list[i].user%}; 网站:{%list[i].site%}</li> $} } {$</ul>$} </script> <script id="neo" type="text/tmpl"> <ul> <& for (var i = 0, l = list.length; i < l; i ++) { &> <li> <&= list[i].index &>. 用户: <&= list[i].user &>; 网站:<&= list[i].site &></li> <& } &> </ul> </script> <script> // 构建测试数据 var data = { test: 'function () {alert("xss")}', list: [] }; // 数据量 var length = 140000; for (var i = 0; i < length; i ++) { data.list.push({ index: i, user: 'tangbin', site: 'http://www.planeart.cn' }); }; // 计时器 var Timer = function (){ this.startTime = + new Date; }; Timer.prototype.stop = function(times){ times = times || 1; return ( new Date - this.startTime)/times; }; // 日志 var log = function (msg) { // 没有必要输出解析代码,很耗费资源 //window.console && console.log(msg); }; function testYayaTemplate() { var yayaTime = new Timer; for(var i=0;i<20;i++){ var yayaContent = YayaTemplate('yaya').render(data); } alert('YayaTemplate在' + (length / 10000) + '万数据编译耗时:' + yayaTime.stop(20)); log(yayaContent); }; function testejs () { var tmplTime = new Timer; for(var i=0;i<20;i++){ var ejsContent = dom.ejs('ejs', data); } alert('ejs在' + (length / 10000) + '万条数据编译耗时:' + tmplTime.stop(20)); log(ejsContent); }; function testneo () { var tmplTime = new Timer; for(var i=0;i<20;i++){ var ejsContent = neo.ejs('neo', data); } alert('neo在' + (length / 10000) + '万条数据编译耗时:' + tmplTime.stop(20)); log(ejsContent); }; function testTmpl () { var tmplTime = new Timer; for(var i=0;i<20;i++){ var tmplContent = tmpl('tmpl')(data); } alert('tmpl 0.2在' + (length / 10000) + '万条数据编译耗时:' + tmplTime.stop(20)); log(tmplContent); }; </script> </head> <body> <button onclick="testTmpl()">tmpl 0.2</button> <button onclick="testejs()">ejsv7</button> <button onclick="testneo()">ejsv8</button> <button onclick="testYayaTemplate()">YayaTemplate</button> </body> </html>

运行代码

糖饼与yaya的都是模仿jQ作者John Resig 的思路,通过环则反复对界定符进行替换,对HTML部分进行转义,但我不相信那一点正则完全实现转义,可以正则构建的代码结构也缺乏对各种特殊情况的处理,要不John Resig就不会搞一个更长更庞大的jquery-tmpl。比如yaya的如果第一行是{% list[0].index%}就报错了,可证一个。如果页面结构更复杂一点,其缺陷就暴露得更多。

更新日志

v1

默认界定符为,当然也可以自定义界定符,只支持当前页面的script元素做模板

http://www.cnblogs.com/rubylouvre/archive/2010/08/10/1796383.html

v2

改进算法提速,比John Resig的 Micro-Templating模板更能应对复杂的模板

http://www.cnblogs.com/rubylouvre/archive/2010/08/22/1805914.html

v3

http://www.cnblogs.com/rubylouvre/archive/2010/08/25/1807789.html

增添了局部模板功能

v4

http://www.cnblogs.com/rubylouvre/archive/2010/08/31/1813122.html

对v3的结构进行优化,支持远程的独立文件做模板

v5

http://www.cnblogs.com/rubylouvre/archive/2010/08/31/1813122.html

尝试新的算法

v6

http://www.cnblogs.com/rubylouvre/archive/2010/10/05/1841933.html

更新默认界定符为,添加新的操作符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值