Javascript模板
1,什么是模板
模板规定了一定的数据呈现结构,并与动态的数据一起呈现出一个具有一定结构的信息。模板在服务器端使用频繁,MVC中的view往往都是通过一定的模板引擎来实现的(比如java的freemarker),业务上将数据统一交给view层,view层通过一定的模板引擎将数据放到模板的适当位置上。不同的模板引擎都会约定一个或者多个模板的写法,以便它来进行处理。前端的模板处理因为需求的不断加强也日渐活跃起来,世面上也出现了大量的模板引擎比如jade,实现原理基本与服务器上的模板引擎类似,都是字符串的处理,但是前端不同于服务器端的是前端可以处理DOM,这样便会开启了另外一种途径来进行数据填充或者替换。这样一个基本的模板处理方法应该具备一个模板的写法约定以及一个数据结构的约定,高级的模板处理还会引入标签的自定义能力以及引入其他模板文件的能力等。
2,尝试写一个基于字符串处理的简单模板处理实现
(1)约定模板写法:
1 <div class='templater'> 2 <div data-config=\'{"aa":"aa"}\'> 3 <% for(var i in data.data){%> 4 <div class='list' data-config='{\"kk\":\"cc\"}'> 5 <div class='name' data-config=\'{"aa":"<%=data.data[i].name%>"}\'><%=data.data[i].name%></div> 6 <div class='value'><%=data.data[i].value%></div> 7 </div> 8 <%}%> 9 </div> 10 </div>
- "<%"与"%>"间可以放入任何javascript代码,必须加“;”结尾,并保证语法正确。
- "<%=%>"是赋值写法。
(2)约定数据结构
标准json格式即可,模板中访问json数据需要通过data命名空间进行,或者说data指向了传入的json。
(3)实现代码如下
1 var template = function() { 2 this.fn = null; 3 }; 4 template.compile = function(temp) { 5 var k = temp.replace(/<%/g, "<%").replace(/%>/g, "%>").replace(/"/g, '"'); 6 var a = k.split(/<%|%>/g), fn = "var out='';", tfn = null; 7 for (var i in a) { 8 var t = a[i].replace(/[\t\n\r ]+/g, " ").trim(); 9 if ((/^=\w*/g).test(t)) { 10 fn += "out+=" + t.substring(1, t.length) + ";"; 11 } else if (/\w*<\w*/g.test(t)) { 12 fn += "out+='" + t.replace(/[\']/g, '"') + "';"; 13 } else if (!/^\s+$/.test(t)) { 14 if (/[\{\}\(\)\[\]]/g.test(t)) { 15 fn += t; 16 } else { 17 fn += "out+='" + t.replace(/[\']/g, '"') + "';"; 18 } 19 } 20 } 21 fn += "return out;"; 22 try { 23 tfn = new Function("data", "fn", fn); 24 } catch (e) { 25 console.log("[template error] " + e.message); 26 } 27 return tfn; 28 }; 29 template.parselite = function(str, data) { 30 str = str.replace(/<%=\w+%>/gi, function(matchs) { 31 var returns = data[matchs.replace(/<%=/g, "").replace(/%>/g, "")]; 32 return returns ? returns : ""; 33 }); 34 return str; 35 }; 36 template.prototype.parse = function(temp, data, fn) { 37 var result = ""; 38 if (!this.fn) { 39 this.fn = template.compile(temp); 40 } 41 if (this.fn) { 42 try { 43 result = this.fn(data, fn); 44 } catch (e) { 45 console.log("[template error] " + e.message); 46 } 47 } 48 return result; 49 };
(4)原理:动态Function,并执行。将模板字符串通过正则表达式将"<%%>"替换成合适的javascript语句,然后如果一切没有问题便可以创建出一个function对象,最后使 之执行,该函数只返回替换数据后的html片段,这样便可以将其插入到dom的合适位置上。
- 优点:javascript文件不是很大,由于只是进行字符串操作而且更新页面只需一次重绘所以性能较好
- 缺点:无法定位模板中错误的位置,不便于调试。
- 注:个人认为前端的模板不应该是大范围的,这样模板本身不会过大,即使模板中出现错误也会较容易处理,所以在小范围实现上以上实现还是可以接受的。
上面的实现不能直接使用,可以稍作处理变成jQuery插件,如下:
1 (function($) { 2 $.template = function() { 3 var temp = new template(), ths = $(this); 4 return { 5 render: function(data, fn) { 6 var str = ths.html(); 7 ths.html("[rending...]"); 8 ths.html(temp.parse(str, data, fn)); 9 return this; 10 } 11 }; 12 }; 13 $.template = function() { 14 var temp = new template(), ths = $(this); 15 return { 16 parse: function(template,data, fn) { 17 return temp.parse(template, data, fn); 18 } 19 }; 20 }; 21 //这里放上面的代码 22 })(jQuery);
使用时可以这样使用:
1 $("#xxx").template().render(data,fn); 2 $.template().parse(template,data,fn);
3,尝试写一个基于dom处理的模板实现
(1)约定模板写法
1 <div> 2 <div key="tt"> 3 <div key="tt1"></div> 4 <div key="tt2"></div> 5 <div key="tt3"></div> 6 <div key="tt4"></div> 7 </div> 8 </div>
"key"值与json数据中的key值对应,如果出现同名key,根据json的数据层级进行dom复制插入。
(2)约定数据结构为json
(3)实现代码
1 var values = function(dom, json, hook) { 2 if (util.isArray(json)) { 3 json = {json: json}; 4 } 5 for (var i in json) { 6 var c = dom.find("*[key='" + i + "']"); 7 if (typeof json[i] === "object") { 8 if (json[i] instanceof Array) { 9 var d = c; 10 for (var m in json[i]) { 11 if (m !== 0) { 12 d = c.clone().appendTo(c.parent()); 13 } else { 14 d = c.parent().children(0); 15 } 16 arguments.callee(d, json[i][m], hook); 17 } 18 } else { 19 arguments.callee(c, json[i], hook); 20 } 21 } else { 22 if (c.length > 0) { 23 if (hook) 24 hook(i, json[i], c); 25 values.setnodevalue(c, json[i]); 26 } 27 } 28 } 29 return dom; 30 }; 31 values.setnodevalue = function(node, value) { 32 var name = node.get(0).nodeName.toLowerCase(); 33 if (values.setter[name]) { 34 values.setter[name](node, value); 35 } else { 36 node.html(value); 37 } 38 }; 39 values.setter = { 40 a: function(node, value) { 41 node.attr("href", value); 42 }, 43 img: function(node, value) { 44 node.attr("src", value); 45 }, 46 input: function(node, value) { 47 node.val(value); 48 }, 49 select: function(node, value) { 50 node.val(value); 51 } 52 };
(4)原理:遍历待处理的整个dom遇到相同key进行数据赋值,根据json的层级自动clone局部dom并插入。
- 优点:清晰未引入浏览器不可识别的元素
- 缺点:大量的dom操作,导致性能不好
- 适用范围:局部的小范围的数据更新
4.在node.js中引入自定义结构以及引入外部模板文件的实现
(1)约定模板
1 <!DOCTYPE> 2 <html> 3 <head> 4 <title>xxxxx</title> 5 </head> 6 <%include("",data,fn)%> 7 <body> 8 <%for(var i in data){%> 9 <div style="margin-bottom: 10px;border-bottom: 1px solid #D7D7D7;"> 10 <div><span>id:</span><span><%=data[i].id%></span></div> 11 <div><span>title:</span><span><%=data[i].title%></span></div> 12 <div><span>content:</span><span><%=data[i].content%></span></div> 13 </div> 14 <%}%> 15 </body> 16 </html>
- "<%"与"%>"间可以放入任何javascript代码,必须加“;”结尾,并保证语法正确。
- "<%=%>"是赋值写法。
- "<%inclue(filename,includedata,functions)%>"引入外部文件并传入模板数据
- "<%username(arguments...)%>"用户自定义方法
(2)约定数据为json
(3)实现代码node.js module
1 var fs = require("fs"); 2 var tag = { 3 include: function(path, a, b, c, d, e, f, g, h, j, k) { 4 try { 5 var data = fs.readFileSync(path); 6 return new template().parse(data.toString(), a, b, c, d, e, f, g, h, j, k); 7 } catch (e) { 8 return ""; 9 } 10 } 11 }; 12 var template = function() { 13 this.fn = null; 14 var k = function() { 15 }; 16 // k.prototype=tag; 17 for (var i in tag) { 18 k.prototype[i] = tag[i]; 19 } 20 this.tag = new k(); 21 }; 22 template.compile = function(temp) { 23 var k = temp.replace(/<%/g, "<%").replace(/%>/g, "%>").replace(/"/g, '"'); 24 var a = k.split(/<%|%>/g), fn = "var out='';", tfn = null; 25 for (var i in a) { 26 var t = a[i].replace(/[\t\n\r ]+/g, " ").trim(); 27 if ((/^=\w*/g).test(t)) { 28 fn += "out+=" + t.substring(1, t.length) + ";"; 29 } else if (/\w*<\w*/g.test(t)) { 30 var cp = t.replace(/[\']/g, '"'); 31 fn += "out+='" + cp + "';"; 32 } else if (!/^\s+$/.test(t)) { 33 if (/[\{\}\(\)\[\]]/g.test(t)) { 34 var istag = false; 35 for (var m in tag) { 36 if (t.indexOf(m) === 0) { 37 if (t.split("(")[0].trim() === m) { 38 istag = true; 39 fn += "out+=this." + t + ";"; 40 break; 41 } 42 } 43 } 44 if (!istag) { 45 fn += t; 46 } 47 } else { 48 fn += "out+='" + t.replace(/[\']/g, '"') + "';"; 49 } 50 } 51 } 52 fn += "return out;"; 53 try { 54 tfn = new Function("data", "fn", fn); 55 } catch (e) { 56 console.log("[template error] " + e.message); 57 } 58 return tfn; 59 }; 60 template.parselite = function(str, data) { 61 str = str.replace(/<%=\w+%>/gi, function(matchs) { 62 var returns = data[matchs.replace(/<%=/g, "").replace(/%>/g, "")]; 63 return returns ? returns : ""; 64 }); 65 return str; 66 }; 67 template.prototype.parse = function(temp, data, fn) { 68 var result = "", fnx = template.compile(temp); 69 if (fnx) { 70 try { 71 result = fnx.call(this.tag, data, fn); 72 } catch (e) { 73 console.log("[template error] " + e.message); 74 } 75 } 76 return result; 77 }; 78 template.prototype.compile = function(temp) { 79 return template.compile(temp); 80 }; 81 template.prototype.simpleParse = function(temp, data) { 82 return template.parselite(temp, data); 83 }; 84 85 exports.template = function() { 86 return new template(); 87 };
(4)原理:在上面第一种实现的基础上会对“<%”其后字符串进行判断,判断其是否在tag对象中,如果存在则调用其对应的函数,这样的函数必须返回字符串。上面已经实现 了在一个模板中引入外部模板文件的方法。
5,总结:
上面演示了基于浏览器以及基于node的javascript简单模板处理的实现方法,实际上只是提供了一个思路。成熟的实现会考虑更多的问题,比如异常定位等,简单的实现则需要更多的约定,与使用者间的约定,只有在约定满足的情况下程序才能正常工作或者减少麻烦。于是乎好像与一句话相遇了“约定优于配置,也称作按约定编程”,从某种程度上说约定的也许是更好的选择,比如在浏览器中运行的脚本,为了保证性能,极限的情况就是不让脚本做任何事情,那么也就是说要尽量减少脚本的工作,如果在可控的范围内可以通过增加人工的约定而减少计算机的运算从而提高性能也未必不是一个好的选择。

浙公网安备 33010602011771号