最簡單的JavaScript模板引擎

 在小公司待久了感受本身的知識面很小,最近逛博客園和一些技術網站看你們在說JavaScript模版引擎的事兒,徹底沒有概念,網上一搜這是08年開始流行起來的。。。原本覺得這是很高深的知識,後來在網上看到jQuery做者John Resig,研究了一下,算是明白了最簡單的javaScript模版引擎的原理,並無想象的那麼高大上,寫篇博客推導一下John Resig寫法的過程,寫出一個最簡單的JavaScript模版引擎。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的實現方式

先看看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

  1. 用正常的方式書寫html
  2. 用<% %>嵌套JavaScript語句
  3. 用<%= %>嵌套JavaScript 變量值

模板轉換爲html字符串原理 

咱們的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了

相關文章
相關標籤/搜索