轉載:https://blog.csdn.net/iteye_16732/article/details/82070065
Mustache是一種Logic-less templates.不支持if這類條件判斷是Logic-less的顯著特征之一.Mustache的另一個特征是體積小,不依賴其他前端類庫,在瀏覽器端和NodeJS中都可以運行.
並非Logic-less.Mustache的體積小,無依賴,前后兼容才是我們當前的項目選擇這套模板系統的真正原因.沒有IF有時候感覺並不給力,所以就想辦法簡單擴展下Mustache,讓其具有一些通用的條件判斷能力.
比如如下的應用場景,我們需要根據某一字段的值,決定輸出有意義的中文,並用顏色加以修飾.
status=="P" ==> <b style="color:green">通過</b>
status=="W" ==> 等待
status=="R" ==> <b style="color:red">拒絕</b>
Logic-less模板實現這個功能就需要在數據上下功夫,如下.
1 data = { 2 list:[ 3 { id:"1",status"P"}, 4 { id:"2",status"W"}, 5 { id:"3",status"R"} 6 ], 7 statusRenderer:function(){ 8 if(this.status=="P"){ 9 return '<b style="color:green">通過</b>' 10 }else if(this.status=="W"){ 11 return '等待' 12 }else{ 13 return '<b style="color:red">拒絕</b>' 14 } 15 } 16 }
這里的statusRenderer就是在數據這邊擴展做的工作,{{{statusRenderer}}}在渲染時,this指向當前context,在取得status之后,經過判斷,return正確的渲染字符串.
於是配合下面的模板就可以滿足我們的要求
1 <ul> 2 {{#list}} 3 <li>ID:{{id}},status:{{{statusRenderer}}}</li> 4 {{/list}} 5 </ul>
項目是很復雜的,如果需要寫無數statusRenderer那會非常累,所以我想Renderer功能強大,在遇到各種特殊情況時,單獨寫一下未嘗不可,但是想上面這種常見需求是需要抽象一下的.
比如我們希望如下這樣的模板,完成同樣的需求.
1 <ul> 2 {{#list}} 3 {{#if(status==P)}}<li>ID:{{id}},status:<b style='color:green'>通過</b></li>{{/endif}} 4 {{#if(status==W)}}<li>ID:{{id}},status:等待</li>{{/endif}} 5 {{#if(status==R)}}<li>ID:{{id}},status:<b style='color:red'>拒絕</b></li>{{/endif}} 6 {{/list}} 7 </ul>
這個改造看起來一定是會傷筋動骨的,因為完全打破了{{#xxx}}{{/xxx}}這種Mustache的嵌套模式.改過之后Mustache就不再是Mustache了.
於是我們在這里妥協下,把{{/endif}}改為{{/if(status==W)}},這樣{{#if(status==W)}}{{/if(status==W)}}就配起對來了.
接下來我們就只要想辦法從模板中把這類標簽正則出來,然后為這類配對自動添加Renderer即可.
模板變成了下面這樣:
1 <ul> 2 {{#list}} 3 {{#if(status==P)}}<li>ID:{{id}},status:<b style='color:green'>通過</b></li>{{/if(status==P)}} 4 {{#if(status==W)}}<li>ID:{{id}},status:等待</li>{{/if(status==W)}} 5 {{#if(status==R)}}<li>ID:{{id}},status:<b style='color:red'>拒絕</b></li>{{/if(status==R)}} 6 {{/list}} 7 </ul>
而輸入給to_html方法的數據則變成如下這樣(里邊的Render為自動生成):
1 data = { 2 list:[ 3 { id:"1",status"P"}, 4 { id:"2",status"W"}, 5 { id:"3",status"R"} 6 ], 7 //下面Renderer為自動生成. 8 "if(status==P)":function(){ 9 if(this.status=="P"){ 10 return true; 11 } 12 return false; 13 }, 14 "if(status==W)":function(){ 15 if(this.status=="W"){ 16 return true; 17 } 18 return false; 19 }, 20 "if(status==R)":function(){ 21 if(this.status=="R"){ 22 return true; 23 } 24 return false; 25 } 26 }
整個改造的大體流程如下,首先從模板中取出if(x.y.z==abc)這樣的key,然后自動生成以"if(x.y.z==abc)"為名字的Renderer.全部代碼如下:
1 function addFns(template, data){ 2 var ifs = getConditions(template); 3 var key = ""; 4 for (var i = 0; i < ifs.length; i++) { 5 key = "if(" + ifs[i] + ")"; 6 if (data[key]) { 7 continue; 8 } 9 else { 10 data[key] = buildFn(ifs[i]); 11 } 12 } 13 } 14 function getConditions(template){ 15 var ifregexp_ig = /\{{2,3}[\^#]?if\((.*?)\)\}{2,3}?/ig; 16 var ifregexp_i = /\{{2,3}[\^#]?if\((.*?)\)\}{2,3}?/i; 17 var gx = template.match(ifregexp_ig); 18 var ret = []; 19 if (gx) { 20 for (var i = 0; i < gx.length; i++) { 21 ret.push(gx[i].match(ifregexp_i)[1]); 22 } 23 } 24 return ret; 25 } 26 function buildFn(key){ 27 key = key.split("=="); 28 var res = function(){ 29 var ns = key[0].split("."), value = key[1]; 30 var curData = this; 31 for (var i = ns.length - 1; i > -1; i--) { 32 var cns = ns.slice(i); 33 var d = curData; 34 try { 35 for (var j = 0; j < cns.length - 1; j++) { 36 d = d[cns[j]]; 37 } 38 if (cns[cns.length - 1] in d) { 39 if (d[cns[cns.length - 1]].toString() === value) { 40 return true; 41 } 42 else { 43 return false; 44 } 45 } 46 } 47 catch (err) { 48 } 49 } 50 return false; 51 }; 52 return res; 53 } 54 // new to_html for exports 55 function to_html(template, data){ 56 addFns(template, data); 57 return Mustache.to_html.apply(this, arguments); 58 }
看起來這樣做的好處是保持了Mustache的配對風格,並且繼續無縫支持原生的嵌套以及否定等語法.
但看起來模板挺丑的,性能損耗也一定是有不少的.
后續的擴展,我想elseif肯定不能支持了,if中帶"與""或"判斷倒是還方便添加的.
當然還可以做很多擴展,比如給數組增加一些內置屬性如"_index_", "_first_", "_last_", "_odd_", "_even_".
Mustache仍然足夠簡單,它本身就具有循環和否定判斷等特性,增加了IF后,稍微加了點邏輯,但能少寫很多Renderer.
重要的是我們依然保有我們所看重的東西:體積小,無依賴,前后兼容.
在我的項目里破壞了Logic-less是我的事情並不接受批判,但請大家根據自己實際情況謹慎選擇.
剛剛接觸Mustache,各種特性還在學習摸索中,現在看起來Lambda和子模板等特性,讓Mustache的JS實現小巧卻功能強大.
或許今天的需求還有更好的解決方案,如果有同學知道還望不吝賜教.