一步一步實現一個前端模板引擎


不要重復發明輪子,這是我聽到最多的一句話,而且現在有很多優秀的模板引擎:handlebar、ejs、artTemplate...那么為什么還要自己實現一個呢?原因不外乎有兩個,
一來是手癢,二來是滿足一點小小的虛榮心:看,模板引擎我也會,簡單!感覺非常優(zhuang)秀(bi)。

既然是自己動手,那么網上的教程肯定先放一邊,突然有點耗子啃南瓜——無從下口的感覺...

一切從需求出發

從后台拿到數據,拼接成字符串放在頁面中,這是我們初入前端時常要做的工作,特別是遇到結構稍微復雜的頁面,光拼接字符串都能搞得你一臉懵逼、二臉懵逼,終於
有一天遇到模板引擎,一邊驚為天人,一邊暗自罵自己傻逼。那么,今天我們動手實現的模板引擎,就從那最初的那一天開始吧!

字符串模板

話說有天接到需求,需要將一組JSON數據,渲染到頁面中。如下所示:

var data = [
    { text: 'text1' ,status:'done' },
    { text: 'text2' ,status:'pending' }
];

var tpl = '<ul>'+
    '<%for(var i = 0, len = data.length; i < len; i++) {%>'+
    '<li class="<%= data[i].status%>"><span><%= data[i].text%></span></li> '+
    '<%}%>'+
    '</ul>';

最初的渲染函數

機智如我自然想到用函數來循環。。。

var render = function(data) {
    var tmp = '';
    tmp += '<ul>';
    for(var i = 0, len = data.length; i < len; i++) {
        tmp += '<li class="'+ data[i].status +'">'+data[i].text+'</li>';
    }
    tmp += '</ul>';
    return tmp;
};

目前來講,我們返回了渲染好的字符串,而且看來工作的很順利。但如果將字符串增加點內容,這個函數就GG思密達了。由此看來,我們需要把字符串模板單獨提取出來,然后再
進行數據渲染。

牛B的Function

我們用的最多的就是 function 關鍵字了,但對於 function 的爸爸 Function 卻有點陌生,那么 Function 究竟哪里流弊呢?紅寶石書不是建議我們不要用 Function嗎?
其實,在JS中,但我們使用 function 聲明函數的時候,JS會自動調用 Function 來生成實例。並且,Function 為我們提供了更強大的武器——動態函數。

語法
var function_name = new Function(arg1, arg2, ..., argN, function_body)

等同於

var function_name = function(arg1,..., argN) {function_body}

於是,我們就有了一把強力的武器,將動態的字符串,放在動態的函數中執行了。

提取字符串構造函數體

有了前面的知識基礎,這一步,我們就要把 tpl 中的字符串,變成 render 的函數體。這就需要另一把武器——正則表達式。利用它,來找到需要渲染數據的位置。

var reg = /<%([\s\S]+?)%>/g

然后,通過 replace 方法替換 reg 找到的位置,構造成函數體!

var template = function(tpl) {
    var reg = /<%([\s\S]+?)%>/g;
    // index 用來記錄替換的位置
    var index = 0;
    // 需要構造的函數體(一步一步和上面的render函數對比)
    var func_body = "var tmp = '';";
    func_body += "tmp += '";

    tpl.replace(reg, function(match, val, offset, str){
        // 每一次匹配到后,截取當前匹配位置和上一次匹配完成后位置之間的字符串
        func_body += tpl.substring(index, offset);

        // 根據 %= 判斷如何進行拼接函數體
        if(match.indexOf('%=') < 0) {
            func_body +="';" + val + ";tmp += '";
        } else {
            func_body += "' + " + val.replace('=', '').trim() + "+'";
        }

        // 完成一次match,改變index 的值
        index = offset + match.length;
        return index;
    });

    // 完成所有匹配后,將剩下的字符串加入
    func_body += tpl.substring(index);
    // 返回 tmp
    func_body += "';return tmp;";
    return func_body;
};

現在,只要我們調用 template 函數,就會返回如 render 的函數體類似的字符串。要使template 函數返回的字符串運行起來,就要用到 Function 了。

var tmpEngine = function (tpl, data) {
    // 返回字符串函數體
    var func_body = template(tpl);
    // 通過 Function 運行
    return new Function('data', func_body).call(null, data);
};

於是,我們調用 tmpEngine, 就可以得到經過數據渲染后的字符串了。

var m = render(tpl2, data2);

console.log('m:' +m);

// m: <ul><li class="done"><span>text1</span></li> <li class="pending"><span>text2</span></li> </ul>

至此,我們的模板引擎的功能層面已經完成,可以愉快的玩耍了。但是!還有很多優化工作等待着推進,這里羅列幾條,周末再戰:

  • 特殊字符轉義,業務可能需要輸出html代碼,減少XSS攻擊
  • 數據為空時的處理
  • 性能

......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM