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

不要重複發明輪子,這是我聽到最多的一句話,並且如今有不少優秀的模板引擎:handlebar、ejs、artTemplate...那麼爲何還要本身實現一個呢?緣由不外乎有兩個,
一來是手癢,二來是知足一點小小的虛榮心:看,模板引擎我也會,簡單!感受很是優(zhuang)秀(bi)。html

既然是本身動手,那麼網上的教程確定先放一邊,忽然有點耗子啃南瓜——無從下口的感受...前端

一切從需求出發

從後臺拿到數據,拼接成字符串放在頁面中,這是咱們初入前端時常要作的工做,特別是遇到結構稍微複雜的頁面,光拼接字符串都能搞得你一臉懵逼、二臉懵逼,終於
有一天遇到模板引擎,一邊驚爲天人,一邊暗自罵本身傻逼。那麼,今天咱們動手實現的模板引擎,就從那最初的那一天開始吧!正則表達式

字符串模板

話說有天接到需求,須要將一組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 爲咱們提供了更強大的武器——動態函數。spa

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

等同於htm

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攻擊
  • 數據爲空時的處理
  • 性能

......

相關文章
相關標籤/搜索