javascript模板系统 ejs v10
最近一直攻略node.js,发现ejsv9在后端的视图层有点力不从心。
后端是模板的最大用户,因此拼字符串必须会死翘翘。通常来说,我们一个action对应一个模板,它应该是只含body部分的HTML,另外,还有一个layout,它是包含head与body的底部。它们两个加起来,加个模型层的数据生成一个真正的页面返给前端。但生成这页面不像普通的挖坑填数字的过程,像ejs、mustache、micro-Templating、doT.js就是如此。不过有的模板可以套嵌大量的逻辑,有的不能,像mustache就号称Logic-less templates,目的不想让模板也成为代码的意大利面条,这是JSP时代的教训。但javaer在struct建立王者统治的年代,腾出许多精力来研究各种东西,对视图层提出两大解决方案,视图helper与标签库。其中标签库因为学习成本过高,因此当其他语言发展出的web framework时,视图helper几乎是他们唯一的选择。视图helper也是返回一段HTML字符串,只不过它与业务层打交道非常频繁而独立出来,而它们独立出来则大大减少视图乱堆代码的现象。helper机制就是ejs v10引入的一种重要改进了。
另外,模板里面的内容有两个地方需要输出到页面,一个是定界符左边与右边的HTML片断,另一个是来自JS逻辑的某些数据,如<%= aaa %>这样的写法(不同每个模板的语法都不一样!)PHP著名的smarty模板有个语法糖,可以对这些将要输出的变量进行进一步加工,即在变量后面加一个|号,后面跟着此过滤器的名字与其传参,而且可以跟多个|跟其他的过滤器。ejs v10也引进此语法糖。
<%$data.circle.title|escape:"html"%> |
在其他重大改进,模板构建算法改进,不再使用转义。传统的前端模板都会对源码中的“'”,“"”,“\\”,“\n”,“\t”,“\f”,“\b”,“\r”进行处理,也就是所谓加双号过程,双引号里面的数据进行转义,防止破坏动态生成的模板函数的结构。ejs v10则将这个HTML数组放到模板函数的外面,从此一劳永逸了!@前缀变量也保证ejs不使用with机制进行对象绑定的。
helper机制的使用,helper函数应该一开始就与模板函数绑在一起的,因此ejs v10的模板函数最初是一个curry函数,它负责传入HTML数组,filter,与helpers。
//helper机制的使用 var fn = $.ejs.compile(source, helper); |
helper是一个对象,里面尽是函数。
生成的是模板函数,如果你是这样调用$.ejs(id, data),它就百分之百缓存了这函数。它里面是调用了$.ejs.compile
现在ejs作为我的newland.js项目的一个模块而存在.
更改日志:
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 更新默认界定符为<& &>,添加新的操作符<&~,对数据源的第一层属性名添加@前缀 v7 对参数进行多态化,因ejs天生支持模板的相互调用便去除<&:与<&~操作符 v8 去掉去掉参数多态化,现在只有两个参数。第一个参数为script标签的ID,第二个参数对数据对象 去掉@标识符,网友反映这东西很怪 去掉远程模板支持,因为怎么远程也一定要同域才行,要不AJAX获取不到,鸡肋。以后模板统一写到type为 "text/html" 的scrpt元素中。 优化quote函数。网上有许多JS模板都是直接用正则进行全文转义,但怎么说也不比上quote函数安全。 使用apply对传参进行优化。indexOf判定优化。 v9 改回v6的形态,后端数据还是带@前缀才方便查看与调试 对输出代码进行修正,去掉用户误写的逗号或分号 v10 helper机制 filter机制 新的构建算法 |
我已经整成jQuery插件了,随便拿去用吧!点我下载
;;( function ($){ // by 司徒正美 //http://www.cnblogs.com/rubylouvre/archive/2012/08/06/2624970.html /** * 入口函数 * @param {string} id CSS表达式(用于模取元素然后取得里面的innerHTML作为源码) * @param {Object} data 数据包 * @return {Object} opts 可选参数,可以自由制定你的定界符 */ $.ejs = function ( id,data,opts){ var el, source if ( !$.ejs.cache[ id] ){ opts = opts || {} var doc = opts.doc || document; data = data || {}; el = $(id, doc)[0]; if (! el ) throw "can not find the target element" ; source = el.innerHTML; if (!(/script|textarea/i.test(el.tagName))){ source = $.ejs.filters.unescape( source ); } var fn = $.ejs.compile( source, opts ); $.ejs.cache[ id ] = fn; } return $.ejs.cache[ id ]( data ); } var isNodejs = typeof exports == "object" ; $.ejs.cache = {}; $.ejs.filters = { //自己可以在这里添加更多过滤器,或者可以到这里面自由提取你喜欢的工具函数 //https://github.com/RubyLouvre/newland/blob/master/system/lang.js escape: function (target) { return target.replace(/&/g, '&' ) .replace(/</g, '<' ) .replace(/>/g, '>' ) .replace(/ "/g, " "" ) .replace(/ '/g, "' "); }, unescape: function (target){ return target.replace(/ "/g,'" ') .replace(/</g, '<' ) .replace(/>/g, '>' ) .replace(/&/g, "&" ); //处理转义的中文和实体字符 return target.replace(/& #([\d]+);/g, function($0, $1){ return String.fromCharCode(parseInt($1, 10)); }); } }; $.ejs.compile = function ( source, opts){ opts = opts || {} var open = opts.open || isNodejs ? "<%" : "<&" ; var close = opts.close || isNodejs ? "%>" : "&>" ; var helperNames = [], helpers = [] for ( var name in opts){ if (opts.hasOwnProperty(name) && typeof opts[name] == "function" ){ helperNames.push(name) helpers.push( opts[name] ) } } var flag = true ; //判定是否位于前定界符的左边 var codes = []; //用于放置源码模板中普通文本片断 var time = new Date * 1; // 时间截,用于构建codes数组的引用变量 var prefix = " ;r += txt" + time + "[" //渲染函数输出部分的前面 var postfix = "];" //渲染函数输出部分的后面 var t = "return function(data){ try{var r = '',line" +time+ " = 0;" ; //渲染函数的最开始部分 var rAt = /(^|[^\w\u00c0-\uFFFF_])(@)(?=\w)/g; var rstr = /([ '"])(?:\\[\s\S]|[^\ \\r\n])*?\1/g var rtrim = /(^-|-$)/g; var rmass = /mass/ var js = [] var pre = 0, cur, code, trim for(var i = 0, n = source.length; i < n; ){ cur = source.indexOf( flag ? open : close, i); if( cur < pre){ if( flag ){//取得最末尾的HTML片断 t += prefix + codes.length + postfix if(cur == -1 && i == 0){ code = source }else{ code = source.slice( pre+ close.length ); } if(trim){ code = $.trim(code) trim = false; } codes.push( code ); }else{ $.error("发生错误了"); } break; } code = source.slice(i, cur );//截取前后定界符之间的片断 pre = cur; if( flag ){//取得HTML片断 t += prefix + codes.length + postfix; if(trim){ code = $.trim(code); trim = false; } codes.push( code ); i = cur + open.length; }else{//取得javascript罗辑 js.push(code) t += ";line"+time+"=" +js.length+";" switch(code.charAt(0)){ case "="://直接输出 code = code.replace(rtrim,function(){ trim = true; return "" }); code = code.replace(rAt,"$1data."); if( code.indexOf("|") > 1 ){//使用过滤器 var arr = [] var str = code.replace(rstr, function(str){ arr.push(str);//先收拾所有字符串字面量 return ' mass ' }).replace(/\|\|/g,"@");//再收拾所有短路或 if(str.indexOf("|") > 1){ var segments = str.split("|") var filtered = segments.shift().replace(/\@/g,"||").replace(rmass, function(){ return arr.shift(); }); for( var filter;filter = arr.shift();){ segments = filter.split(":"); name = segments[0]; args = ""; if(segments[1]){ args = ' , ' + segments[1].replace(rmass, function (){ return arr.shift(); //还原 }) } filtered = "$.ejs.filters. "+ name +" ( " +filtered + args+" ) " } code = " = "+ filtered } } t += " ;r + " +code +" ; " break; case " #"://注释,不输出 break case "- ": default://普通逻辑,不输出 code = code.replace(rtrim,function(){ trim = true; return " " }); t += code.replace(rAt," $1data. ") break } i = cur + close.length; } flag = !flag; } t += " return r; } catch (e){ $.log(e);\n$.log(js "+time+" [line "+time+" -1]) }} " var body = [" txt "+time," js "+time, " filters "] var fn = Function.apply(Function, body.concat(helperNames,t) ); var args = [codes, js, $.ejs.filters]; //console.log(fn+" ") return fn.apply( this , args.concat(helpers)); } return $.ejs; })(jQuery) |
< script type="tmpl" id="table_tmpl"> <&= title() &> < table border=1> <&- 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 > </ tr > <& } &> </ table > <&# 怎么可能不支持图片 &> < img src="<&= @href &>"> </ script > < script > $(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 html = $.ejs("#table_tmpl",{ trs: trs, href: "http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_type4.jpg" },{ title: function(){ return "< p >这是使用视图helper输出的代码片断</ p >" } }); $("#ejsv10").html(html) }) </ script > |
这是我模板引擎生成的函数,里面看不到要转义的字符串!

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步