效果預覽 首先,按照慣例,我們先看doT 實現的效果: 模板: {{? it.name }} <div>嗨, {{=it.name}}!</div> {{?? it.age 0}} <div>我猜應該還沒人給你起名字吧?</div> {{??}} 你已經 {{=it.age}} 歲了但是你還沒有名字 ...
效果預覽
首先,按照慣例,我們先看doT 實現的效果:
模板:
{{? it.name }} <div>嗨, {{=it.name}}!</div> {{?? it.age === 0}} <div>我猜應該還沒人給你起名字吧?</div> {{??}} 你已經 {{=it.age}} 歲了但是你還沒有名字? {{?}}
數據 | 結果 |
---|---|
{name:'十一川'} |
<div>嗨, 十一川! </div> |
{age:0} |
<div>我猜應該還沒人給你起名字吧?</div> |
{age:11} |
<div>你已經 11 歲了但是你還沒有名字?</div> |
實現思路
我們此次要做的事情,與上次一樣,無非是一個 查找標記 => 替換成對應代碼 的過程 而區別在於,我們此次要替換的標記不再是一個,而是4個:
標記 | 結果 | |
---|---|---|
{{? it.name }} | => | if( it.name ){ |
{{?? it.age === 0}} | => | } else if( it.age === 0 ) { |
{{??}} | => | } else{ |
{{?}} | => | } |
實現過程
寫出正則表達式
var IF = /\{\{\?([^\?]{1}.*?)\}\}/g; var ELSE_IF = /\{\{\?\?(.+?)\}\}/g; var ELSE = /\{\{\?\?\}\}/g; var END_IF = /\{\{\?\}\}/g;
這裡需要特別註意的是,匹配if
與其他標記的包含關係,也就是說,如果我們匹配的是:
那麼,else
標記的{{??}}
中的第二個?
會被當成條件,也就是說:
甚至於,我們作為結尾的{{?}}
也會被當成沒有條件的if語句:
這很明顯不是我們想要達到的效果啊!
要解決這個問題,首先能想到的簡單粗暴的辦法就是。。。我們可以先把{{??}}
跟{{?}}
標記替換掉嘛,這樣不就等到替換{{? 條件}}
的時候,我們就能保證全部都是條件語句了。
唔。。。這固然是個“暫時能用的辦法”,但是萬一哪天,我們心血來潮地把賦值{{= 值}}
改成了這樣:{{??? 值}}
,那我們還得去代碼里看看值的替換是否在if替換之前?
有!我們不難推導出,這裡的“條件”要先符合兩個條件:
- 第一個字元不能是
?
這個字元本身 - 不能為空
這樣的話,不管我們替換的順序如何,都能保證不會影響到其他的標記,那麼,這兩個限定又如何通過正則表達式表達出來呢?
我們可以通過^
符號來表示“不能是某個字元”,而我們要表示“不能是?的一個字元”的話自然就是[^\?]
;而{1}
來表示“有且只有一個字元”。因此([^\?]{1}.*?)
表示的就是“最起碼一個字元,而且第一個字元不能是?
的任意長度字元串” ,自然就是我們一開始提到的if的正則表達式。
順帶一提,為什麼只限制了“第一個字元不能是?
這個字元”,而不是“所有字元”呢?這是因為三元操作符?:
能夠產生一個合法的bool值,比如:a?2:3
,如果我們只是粗暴地規定“所有的條件中都不能包含?
”那麼可能會導致原本合法的表達式沒有被當成條件。而這個表達式的?前面最起碼要有一個變數名,變數名最短也是一個非?的字元,因此在這個層面,我們僅僅限定住“第一個字元”,是有著必要性和充分性的。
而類似的,為了防止 else 的標記被當成“沒有條件的 else if 標記” 我們同樣要對這裡的條件作出“起碼有一個字元的限制” ,也就是(.+?)
引入out
現在,我們還有一個問題是,因為引入了判斷機制,我們編譯之後的代碼不能再像之前那樣像一條串似的直接拼接起來。我們在第一篇中編譯後的代碼大概類似於這樣:return 'H1!'+it.name;
可是,我們只進行了判斷,沒有進行字元串拼接的話,代碼大概是這樣:
return if(it.name){ 'Hi!'+it.name; }
連語法都不正確了啊喂!不要慌,總之先冷靜下來找時光機。。。啊不對,是先回想正常情況下我們是如何完成類似的拼接的:
var out=""; if(it.name){ out += 'Hi!'+it.name; } return out;
因此類似的,我們要引入一個變數out
,這樣我們就能在判斷體內把結果拼接起來了。於是,我們要替換的目標變成了這樣:
標記 | 結果 | |
---|---|---|
{{? it.name }} | => | '; if( it.name ){ out += ' |
{{?? it.age === 0}} | => | '; } else if( it.age === 0 ) { out += ' |
{{??}} | => | '; } else{ out += ' |
{{?}} | => | '; } out += ' |
我們能這麼做的前提,是我們假設,在進行判斷語句之前,這個字元串還沒有結束。 於是,我們加上單引號使其提前結束(我們使用單引號來標記字元串,還記得嗎?)在執行完判斷之後,我們又通過out+='
來繼續拼接字元串。也就是說,在執行判斷之前,我們的字元串是未結束的狀態,在執行完判斷之後,我們的字元串依然是未結束的狀態
實現過程
好了,剩下的就只有將剛剛的思考轉化為實際代碼的過程了,我建議你自己手動完成這部分,如果你實在不知如何下手的話,這裡有一份代碼供你參考:
HTML:
<div id="target"></div> <script type="text/x-dot-template" class="js-template"> {{? it.name }} <div>嗨, {{= it.name}}!</div> {{?? it.age === 0}} <div>我猜應該還沒人給你起名字吧?</div> {{??}} <div>你已經 {{= it.age}} 歲了但是你還沒有名字?</div> {{?}} </script>View Code
JavaScript(需要先引入jQuery):
var jst = {};
var VALUE = /\{\{\=(.*)\}\}/g;
var IF = /\{\{\?([^\?]{1}.*?)\}\}/g;
//var IF = /\{\{\?([^\?]+.*?)\}\}/g;
var ELSE_IF = /\{\{\?\?(.+?)\}\}/g;
var ELSE = /\{\{\?\?\}\}/g;
var END_IF = /\{\{\?\}\}/g;
jst.template = function(templateText) {
// ↓這個就是實際運行的模板函數
return function(it) {
// 值綁定
// var result = templateText;
var result = templateText.replace(VALUE, "'+($1)+'");
var ev = result;
// if && else
// 這裡是為了把字元 if else 裡面的內容提取出來
ev = ev.replace(IF, "'; if(($1)){out +=' ")
.replace(ELSE, "'}else{out += ' ")
.replace(ELSE_IF, "';}else if(($1)){ out += ' ")
.replace(END_IF, "'}; out+=' ");
ev = "var out = ''; out+=' " + ev.split('\n').join("") + "' ;";
//console.log(ev);
// debugger
return eval(ev);
}
}
//運行部分:
var func = jst.template($('.js-template').html());
$('#target').html(func({
name:'十一川',
age:0
}));
View Code