最簡單的JavaScript模板引擎

 在小公司待久了感覺自己的知識面很小,最近逛博客園和一些技術網站看大家在說JavaScript模版引擎的事兒,完全沒有概念,網上一搜這是08年開始流行起來的。。。本來以爲這是很高深的知識,後來在網上看到jQuery作者John Resig,研究了一下,算是明白了最簡單的javaScript模版引擎的原理,並沒有想象的那麼高大上,寫篇博客推導一下John Resig寫法的過程,寫出一個最簡單的JavaScript模版引擎。

什麼是JavaScript引擎

 其實在網站開發中模板還是很常見的一種技術,比如PHP的Smarty、ASP.NET的Master Page等,但這些模板都是基於服務器的,JavaScript模板引擎是爲了解決我們在前端寫出形如這樣的拼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>

 看了很容易就明白開發者希望得到的是這樣的html

<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引擎吧。

 模板的語法

模板的語法很簡單,有三條基本規則

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

模板轉換爲html字符串原理 

我們的JavaScript引擎正式設計爲識別這種類型的模板的,拿上面的做例子,這樣的一個模版

<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了

發佈了238 篇原創文章 · 獲贊 269 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章