不要重復發明輪子,這是我聽到最多的一句話,而且現在有很多優秀的模板引擎: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攻擊
- 數據為空時的處理
- 性能
......