在小公司待久了感受本身的知识面很小,最近逛博客园和一些技术网站看你们在说JavaScript模版引擎的事儿,彻底没有概念,网上一搜这是08年开始流行起来的。。。原本觉得这是很高深的知识,后来在网上看到jQuery做者John Resig,研究了一下,算是明白了最简单的javaScript模版引擎的原理,并无想象的那么高大上,写篇博客推导一下John Resig写法的过程,写出一个最简单的JavaScript模版引擎。javascript
其实在网站开发中模板仍是很常见的一种技术,好比PHP的Smarty、ASP.NET的Master Page等,但这些模板都是基于服务器的,JavaScript模板引擎是为了解决咱们在前端写出形如这样的拼html的语句html
var html='<ul>'; for(var i=0;i<users.length;i++){ html+='<li><a href=">'+users[i].url+'">'+users[i].name+'</a>'; } html+='</ul>'; document.getElementById('results').innerHTML=html;
上面的代码咱们一看就知道是在拼html,但具体拼的什么很难说清,须要逐句去读代码,若是咱们有这样一个模板前端
<ul> <% for ( var i = 0; i < users.length; i++ ) { %> <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li> <% } %> </ul>
看了很容易就明白开发者但愿获得的是这样的htmljava
<ul> <li><a href="XXX">OOO</a></li> <li><a href="XXX">OOO</a></li> <li><a href="XXX">OOO</a></li> </ul>
JavaScript模板引擎就是帮咱们把带有JavaScript代码的伪html语句翻译为html的东东正则表达式
先看看John Resig是怎么实现最简单的一个JavaScript模板引擎的数组
1 // Simple JavaScript Templating 2 // John Resig - http://ejohn.org/ - MIT Licensed 3 (function(){ 4 var cache = {}; 5 6 this.tmpl = function tmpl(str, data){ 7 // Figure out if we're getting a template, or if we need to 8 // load the template - and be sure to cache the result. 9 var fn = !/\W/.test(str) ? 10 cache[str] = cache[str] || 11 tmpl(document.getElementById(str).innerHTML) : 12 13 // Generate a reusable function that will serve as a template 14 // generator (and which will be cached). 15 new Function("obj", 16 "var p=[],print=function(){p.push.apply(p,arguments);};" + 17 18 // Introduce the data as local variables using with(){} 19 "with(obj){p.push('" + 20 21 // Convert the template into pure JavaScript 22 str 23 .replace(/[\r\t\n]/g, " ") 24 .split("<%").join("\t") 25 .replace(/((^|%>)[^\t]*)'/g, "$1\r") 26 .replace(/\t=(.*?)%>/g, "',$1,'") 27 .split("\t").join("');") 28 .split("%>").join("p.push('") 29 .split("\r").join("\\'") 30 + "');}return p.join('');"); 31 32 // Provide some basic currying to the user 33 return data ? fn( data ) : fn; 34 }; 35 })();
看完上面代码就明白的同窗就不用看下面内容了,没太明白的同窗能够和我一起看看着三十多句代码为何可以实现一个JavaScript引擎吧。服务器
模板的语法很简单,有三条基本规则app
咱们的JavaScript引擎正式设计为识别这种类型的模板的,拿上面的作例子,这样的一个模版ide
<ul> <% for ( var i = 0; i < users.length; i++ ) { %> <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li> <% } %> </ul>
想获得预期html字符串,咱们必须设法让模板内部的javascript变量置换、javaScript语句执行,也就是把JavaScript代码剥离出来执行,把其它html语句拼接为一个字符串函数
var p=[]; p.push('<ul>'); for(var i=0;i<users.length;i++){ //javascript语句执行 p.push('<li><a href="'); //html语句拼接
p.push(users[i].url); //javascript变量置换后拼接 p.push('">'); p.push(users[i].name); p.push('</a></li>'); } p.push('</ul>');
最后获得的数组join一下就是咱们但愿获得的字符串了,首先须要取到模板内的字符串,这个简单按照John的作法咱们能够把模板放到一个script标签里(防止在页面显示出来),换成咱们特定的类型
<script type="text/html" id="user_tmpl"> <ul> <% for ( var i = 0; i < users.length; i++ ) { %> <li> <a href="<%=users[i].url%>"> <%=users[i].name%> </a> </li> <% } %> </ul> </script>
这样就能够经过 document.getElementById(str).innerHTML 来获取模版内字符串了,而后咱们应用一些简单的法则处理一下模板内字符串
<%=xxx%> => ');p.push(xxx);p.push(' <% => '); %> => p.push('
这样咱们就能够获得这样的结构,看起来就已经很接近结果了
p.push('<ul>'); for(var i=0;i<users.length;i++){ p.push('<li><a href="'); p.push(users[i].url); p.push('">'); p.push(users[i].name); p.push('</a></li>'); } p.push('</ul>');
如今咱们根据上面规则作替换了,这里得使用一些正则表达式和replace函数的知识,不太熟悉的同窗可能须要看看 JavaScript 正则表达式上——基本语法 JavaScript正则表达式下——相关方法
1.把<%=xxx%> 替换为 ');p.push(xxx);p.push('
html=html.replace(/<%=(.*?)%>/g,"');p.push(xxx);p.push('");
2.把<%替换为 ');
html=html.replace(/<%/g,"');");
3.把%> 替换为 p.push('
html=html.replace(/%>/g,"p.push('");
咱们再把结果用p.push(' 和 '); 包裹起来就能够看到初步效果了
function tmpl(id,data){ var html=document.getElementById(id).innerHTML; var result="var p=[]; p.push('" +html.replace(/<%=(.*?)%>/g,"');p.push(xxx);p.push('") .replace(/<%/g,"');") .replace(/%>/g,"p.push('") +" ');"; }
这样咱们就把html模版内容替换成了这样的一个字符串
var result="
var p=[]; p.push('<ul>'); for(var i=0;i<users.length;i++){ p.push('<li><a href="'); p.push(users[i].url); p.push('">'); p.push(users[i].name); p.push('</a></li>'); } p.push('</ul>');"
貌似获得结果了,但咱们获得的是字符串,咱们预期的是这个字符串执行的结果,不少同窗会想到使用eval就可让字符串变成JavaScript语句执行,可是Jonh使用了另一种方式——建立function,咱们知道除了经常使用使用function关键字建立一个function
function fn(data){ console.log(data); }
还可使用Function构造函数来建立一个function
var fn = new Function(arg1, arg2, ..., argN, function_body)
在上面的形式中,每一个 arg 都是一个参数,最后一个参数是函数主体(要执行的代码),使用这种方式能够动态(方法体是动态生成的,提早不知道,固然这样作会有效率问题)建立一个方法,也就是说咱们还可使用刚才拼出来的javascript字符串动态建立一个函数
function tmpl(id,data){ var html=document.getElementById(id).innerHTML; var result="var p=[];p.push('" +html.replace(/[\r\n\t]/g," ") .replace(/<%=(.*?)%>/g,"');p.push($1);p.push('") .replace(/<%/g,"');") .replace(/%>/g,"p.push('") +"');return p.join('');"; var fn=new Function(data,result); return fn(data); }
这样看起来很科学了,可是咱们执行一下会报错,缘由很简单就是参数的做用域不对,咱们须要改变一下动态构造的方法的做用域,这个有不少方式好比apply函数啊什么的,咱们暂且采用John的方式——使用with关键字改变做用域
function tmpl(id,data){ var html=document.getElementById(id).innerHTML; var result="var p=[];with(obj){p.push('" +html.replace(/[\r\n\t]/g," ") .replace(/<%=(.*?)%>/g,"');p.push($1);p.push('") .replace(/<%/g,"');") .replace(/%>/g,"p.push('") +"');}return p.join('');"; var fn=new Function("obj",result); return fn(data); }
虽然看起来和John的方法还有很大区别,不过咱们已经偷师到了其精髓,实现了一个最简单JavaScript模版引擎,你是否是也明白了JavaScript模版引擎是什么了呢?就是简单的字符串替换,剥离出JavaScript语句,而后利用新的字符串构造函数,返回结果。
<!DOCTYPE html> <html> <head> <title>Template</title> </head> <body> <div id="results"></div> <script type="text/html" id="user_tmpl"> <ul> <% for ( var i = 0; i < users.length; i++ ) { %> <li><a href="<%=users[i].url%>"><%=users[i].name%></a></li> <% } %> </ul> </script> <script type="text/javascript"> var results = document.getElementById("results"); var users=[ {"name":"Byron", "url":"http://localhost"}, {"name":"Casper", "url":"http://localhost"}, {"name":"Frank", "url":"http://localhost"} ]; function tmpl(id,data){ var html=document.getElementById(id).innerHTML; var result="var p=[];with(obj){p.push('" +html.replace(/[\r\n\t]/g," ") .replace(/<%=(.*?)%>/g,"');p.push($1);p.push('") .replace(/<%/g,"');") .replace(/%>/g,"p.push('") +"');}return p.join('');"; var fn=new Function("obj",result); return fn(data); } results.innerHTML = tmpl("user_tmpl", users); </script> </body> </html>
应用了简单的JavaScript模版引擎,咱们能够很方便的拼出一些html了