模板引擎原理&實例

本文引至: 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

new Funciton()

通常咱們定義一個函數, 最快捷的辦法就是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

replace

由於獲取的是一個字符串.因此,咱們一般須要使用正則來進行簡單的匹配. 最早想到的就是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的方法.

push方式-John

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). 到這裏,模板引擎這塊已經到了一個峯值了. 後面的難點就是如何進行模板的更新和替換了.

相關文章
相關標籤/搜索