本文引至: please call me HRjavascript
js的模板匹配是頁面渲染很重要的一塊. 不管是後端的同構,仍是前端ajax拉取. 若是數據複雜, 那麼使用js模板引擎將會是一個很是方便的工具. 經常使用的就有arTemplate, mustache.js等. 一個具體的特徵表示符就是:<%= %>
和<% %>
. 固然,還有mustache的{{ }}
. 不過,這裏咱們先不談這些虛的, 咱們來實現一個簡單的模板引擎.前端
首先,模板引擎的工做就是,將你的template轉化爲實際的HTML。 更具體來講,就是將template轉化爲string.java
// template <ul> <% for(var i in items){ %> <li class='<%= items[i].status %>'><%= items[i].text %></li> <% } %> </ul> // 實際轉化爲 var temp = '<ul>'; for(var i in items){ temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>"; } temp += '</ul>';
上面想表達的意思就是,如何將template 合理的轉化爲一個string.
這裏, 咱們主要針對<%= %>
和<% %>
來進行講解.
這個簡單的引擎主要涉及到兩個只是點,一個是new Funciton(){},還有一個是replace.react
通常咱們定義一個函數, 最快捷的辦法就是jquery
function a(param){ body... } // 或者 var a = function(param){ body... } //不多有 var a = new Function(param,body); // 而且body裏面只能是string類型.
不過,咱們這裏就是使用這個body的string類型來完成字符串的解析.
看個實例:webpack
var str = `var temp = '<ul>'; for(var i in items){ temp += "<li>" + items[i] + "</li>"; } temp += '</ul>'; return temp; ` var render = new Function("items",str); console.log(render([1,2,3])); // 返回 // <ul><li>1</li><li>2</li><li>3</li></ul>
另一個就是,replace.git
由於獲取的是一個字符串.因此,咱們一般須要使用正則來進行簡單的匹配. 最早想到的就是match. 可是,他是一次性輸入結果,不能在循環當中,進行字符串的獲取. 這裏,就須要使用到replace這個方法. 他有一個內在的feature.即, 若是你使用正則的global模式,他會執行所有匹配和替換.基本格式爲:github
str.replace(regexp|substr, newSubStr|function)
主要看一下後面帶函數的內容:web
function(match,p1,..pn,offset,string){}
match: 表示匹配到的字符串. 無論怎樣都要進行返回. 這樣才能保證最終的字符串完整.ajax
p1...pn: 這是正則分組的結果.根據你的()
來肯定,你有多少個選項.
offset: 當前匹配字符在整個字符中的起始位置.至關於indexOf(xx)返回的內容.
string: 原始字符串
這裏,須要說明一點, replace後面的function並非只會執行一次,他會執行屢次.由於,他是按照正則匹配到的順序執行的(執行的是惰性匹配)
看一個簡單的demo:
function replacer(match, p1, p2, offset, string) { if(match){ return 2; } return match; } // 將匹配到的內容,所有換爲2. var newString = 'abc12345#$*%'.replace(/(\d+)|([^\w]*)/g, replacer); console.log(newString); // 返回 abc22
這應該就算是比較簡單的了. 接下來,咱們來正式的看一看模板引擎具體的流程.
咱們這裏主要是針對<% %>
和<%= %>
. 這裏,先放出兩個正則匹配:
var evaluate = /<%([\s\S]+?)%>/; // <% %> var interpolate = /<%=([\s\S]+?)%>/; // <%= xx %>
有童鞋,可能會疑惑爲何變量名會是這兩個. 實際上,這是ERB
模板原理提出的兩個基本概念. 至關於就是,一個是變量替代,一個是直接渲染而已.
關鍵點其實並不在這, 而是在若是將一個template拼接爲一個function_body. 這md纔是真難.
還記得上面的格式是:
var temp = '<ul>'; for(var i in items){ temp += "<li>" + items[i] + "</li>"; } temp += '</ul>'; return temp;
簡單的說就是, 將<% %>直接拼接+=
,以後又是temp+=便可. 而<%= %>
則直接是變量名的渲染.
寫一下僞代碼就是:
if(interpolate){ function_body+="';"+interpolate+"temp+='"; } if(evalute){ function_body+="'"+evalute+"'"; }
結合replace 中回調function 內容, 這裏直接將正則匹配寫爲優先級.
var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g // 後面的$ 是用來匹配最後截斷的字符串.
在匹配的時候,須要注意,將\r\n
這個給escaper掉,否則,後面出bug都不知道是怎麼弄出來的. 由於正則有時候是不會給你作這個工做的.轉義也很簡單.直接將\r
變爲\\r
便可. 由於在實際的render中,瀏覽器會自動識別的.咱們這裏主要是讓他在第一次compile時,將換行給去掉.
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; text = text.replace(escaper,'');
react在處理這個JSX的時候,也是使用這種方式,將全部的換行符所有給escape掉. 則總的代碼爲:
var str = ` <ul> <% for(var i in items){ %> <li><%= items[i] %></li> <% } %> </ul> `; var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g //模板文本中的特殊字符轉義處理 var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; //text: 傳入的模板文本字串 //data: 數據對象 var template = function(text,data){ var index = 0;//記錄當前掃描到哪裏了 text = text.replace(escaper,''); var function_body = "var temp = '';"; function_body += "temp += '"; text.replace(matcher,function(match,interpolate,evaluate,offset){ //找到第一個匹配後,將前面部分做爲普通字符串拼接的表達式 //添加了處理轉義字符 function_body += text.slice(index,offset); // .replace(escaper, function(match) { return '\\' + escapes[match]; }); //若是是<% ... %>直接做爲代碼片斷,evaluate就是捕獲的分組 if(evaluate){ function_body += "';" + evaluate + "temp += '"; } //若是是<%= ... %>拼接字符串,interpolate就是捕獲的分組 if(interpolate){ function_body += "' + " + interpolate + " + '"; } //遞增index,跳過evaluate或者interpolate index = offset + match.length; //這裏的return沒有什麼意義,由於關鍵不是替換text,而是構建function_body return match; }); //最後的代碼應該是返回temp function_body += "';return temp;"; var render = new Function('items', function_body); return render(data); } console.log(template(str,[1,2,3]));
上面這種方法,應該是較通常的方法渲染的快一點, 由於他只涉及到字符串的拼接和調用Function的渲染函數.
不過, 這裏我仍是要祭出jquery做者,John Resig寫的Micro-Templating的方法.
John寫的方式,應該算是大部分模板共同使用的一種方式. 採用先拼接後渲染.
function tmpl(str, data){ var fn = new Function("obj", "var p=[],print=function(){p.push.apply(p,arguments);};" + "with(obj){p.push('" + str .replace(/[\r\t\n]/g, " ") .split("<%").join("\t") .replace(/((^|%>)[^\t]*)'/g, "$1\r") .replace(/\t=(.*?)%>/g, "',$1,'") .split("\t").join("');") .split("%>").join("p.push('") .split("\r").join("\\'") + "');}return p.join('');"); return data ? fn(data) : fn; }
簡單的來講就是:
// 原始模板 'My skills:' + for(var index in this.skills) { + '<a href="">' + this.skills[index] + '</a>' + } // 編譯 var r = []; r.push('My skills:'); for(var index in this.skills) { r.push('<a href="">'); r.push(this.skills[index]); r.push('</a>'); } return r.join('');
經過push操做,將指定HTML插入,而且加上數據渲染. 這種方式,實際上和上面的差異就在於拼接這一塊. 使用push進行拼接的,要比使用+=
拼接的慢4倍左右. 不過,這在單次渲染過程當中,並無什麼太大的影響.
再次聲明上面兩種方式是很是初級和不安全的. 由於沒作任何的escape,而且在性能上也是有點欠缺的. 如今比較流行的模板引擎主要有: mustache.js,artTemplate.
mustache.js是以他獨有的語法格式, like: {{#name}},{{/name}} 來實現 for,if等邏輯判斷的. 他相對於之前的ERB引擎來講, 速度快,語法簡潔(但也難學...)
而後就是artTemplate, artTemplate 較其餘引擎比起來就比較快了. 或者,咱們也能夠僅僅把他叫作模板,由於,他能夠實現預編譯(precompile). 即,將引擎在瀏覽器中作的那一部分,挪到開發者自動編譯環節. 這裏,咱們來簡單說一下預編譯.
什麼叫作預編譯呢? 這估計看到這個名詞,有點bigger的感受. 但實際上, 他作的工做,就是上面咱們寫的兩個引擎作的事, 他經過gulp或者webpack自動實現編譯函數的生成和合並.即:
// 原始template 'My skills:' + for(var index in this.skills) { + '<a href="">' + this.skills[index] + '</a>' + } // 在部署時候進行編譯,把template 經由引擎自動生成一個函數 function preCompile(){ var r = []; r.push('My skills:'); for(var index in data.skills) { r.push('<a href="">'); r.push(data.skills[index]); r.push('</a>'); } return new Function('data',r.join('')); }
坊間傳聞,artTemplate使用預編譯的模板來和其餘的模板引擎作比較,而後證實他的性能高超... 俺artTemplate比mustache快xxx倍, 牛逼麼?
看到這裏, 我就呵呵了一句. 親, 您用到引擎了嗎? 你頂多使用了函數...
但,不得不認可,artTemplate可以想到使用precompile,而且作的很棒,這是值的確定的. 後來,TmosJS的出現,讓前端模板可使用模塊化進行組合(好比,include). 到這裏,模板引擎這塊已經到了一個峯值了. 後面的難點就是如何進行模板的更新和替換了.