Micro Templating源碼分析

關於模板,寫頁面的人們其實一直在用,asp.net , jsp , php, nodejs等等都有他的存在,固然那是服務端的模板。 前端模板,做爲前端人員確定是多少有接觸的,Handlebars.js,JsRender,Dust.js,Mustache.js,Underscore templates,Angularjs,Vuejs,reactjs處處都離不開模板的影子。
關於前端模板的分類,我會在單獨的博客來和你們一塊兒學習。
本文主要是分析一下jQuery的創始人的Micro-Templating,麻雀雖小缺五張俱全。
先貼出做者的源碼:javascript

// Simple JavaScript Templating
// John Resig - https://johnresig.com/ - MIT Licensed
(function(){
  var cache = {};
   
  this.tmpl = function tmpl(str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
       
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
         
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
         
        // Convert the template into pure JavaScript
        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('');");
     
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
})();

基本原理:

  1. 使用屬性檢查來進行緩存
  2. 採用正則替換標籤(賦值標籤,js語句標籤)
  3. 使用with設置代碼在對象中的做用域,主要是提高了編程體驗,(固然也能夠用apply,call,bind等修改函數做用域,而後經過this.prop來編寫,可是體驗上差一些)
  4. 動態構建執行函數
  5. 經過判斷決定返回結果類型

關於 1,3,5沒有太多須要講的,關於5,若是執行時不傳入data參數,返回的執行函數,能夠延遲使用,處處使用。php

print

重點在於2和4,在這以前,先看看print,這個print申請在函數頂部,就表示在js語句的時候是能夠調用呢,怎麼調用呢,看看示例,至於做用麼,固然是debug啊html

<script type="text/html" id="item_tmpl">         
        <% for ( var i = 0; i < items.length; i++ ) { %>    
            <% if( i%2 == 1) {%>
                <li><%=items[i].id%>:<%=items[i].name%></li>
            <% } %> 
        <% } %>
        <% print('數組長度' + items.length ); %>
        <div style='background:<%=color%>'><%=id%></div>

      </script>

很簡單: <% print('數組長度' + items.length ); %>
原理也很簡單,數組p裏面添加一條數據前端

正則替換

爲了方便debug和備註,我調整一下原理結構java

(function () {
    var cache = {};

    this.tmpl = function tmpl(str, data) {
        // Figure out if we're getting a template, or if we need to
        // load the template - and be sure to cache the result.
        var fn = !/\W/.test(str) ?
            cache[str] = cache[str] ||
            tmpl(document.getElementById(str).innerHTML) :

            // Generate a reusable function that will serve as a template
            // generator (and which will be cached).
            new Function("obj",
                "var p=[],print=function(){p.push.apply(p,arguments);};" +

                // Introduce the data as local variables using with(){}
                "with(obj){p.push('" +

                // Convert the template into pure JavaScript
                getStr(str)
                + "');}return p.join('');");

        // Provide some basic currying to the user
        return data ? fn(data) : fn;
    };


    function getStr(str){
         // 刪除回車,製表,換行
        str = str .replace(/[\r\t\n]/g, " ");   
        // 替換 <% 爲 \t製表符,兩種狀況(賦值和js代碼)
        // 賦值: 例如 <div id="<%=id%>">  ==>  <div id="\t=id%>">
        // js代碼:例如 <% for ( var i = 0; i < items.length; i++ ) { %>  ==>  \t for ( var i = 0; i < items.length; i++ ) { %>
        str = str.split("<%").join("\t"); 
        // 替換'爲\r ,最後一步會從新替換回來 
        // 節點屬性操做賦值使用單引號,若是不替換 ,''>' 是會報錯的
        // <div style='background:<%=color%>'><%=id%></div>   ==> p.push(' <div style='background:',color,''>',id,'</div>        ');            
        str = str.replace(/((^|%>)[^\t]*)'/g, "$1\r");
        // 賦值解析:賦值後部分,拆分爲三項,結合with,id就會成爲實際的值,而後一直被push  <div id="\t=id%>"> ==>    <div id=" ,id, ">
        // 這裏會消費掉 <%=xxx%>,
        // 那麼剩下的 %>必然是js語句結尾的, \t必然是js語句的開頭
        str = str.replace(/\t=(.*?)%>/g, "',$1,'");   
        //js語句開始符號替換: 通過上一步後,還剩餘的\t,是js語句的,這裏就用 ');來結束 ,js語句會單開p.push, 
        str = str.split("\t").join("');");        
        // js語句結尾符號替換: %> 替換爲 p.push, 這裏把js語句內生成的字符串或者變量再push一次
        str = str.split("%>").join("p.push('");
        // 替換回車爲\' , 恢復str.replace(/((^|%>)[^\t]*)'/g, "$1\r") 去掉的'  
        str = str.split("\r").join("\\'");  
        
        return str;
    }
})();

上面頗有意思的是,先徹底替換了\r\t,而後再用\r\t做爲佔位符。
\t做爲<%的佔位符,\r做爲特定條件下'的佔位符。node

咱們接下來按照正則替換一步異步來分析react

模板源碼
<% for ( var i = 0; i < items.length; i++ ) { %>    
        <% if( i%2 == 0) {%>
            <li><%=items[i].id%>:<%=items[i].name%></li>
        <% } %> 
    <% } %>
    <% print('數組長度' + items.length ); %>
    <div style='background:<%=color%>'><%=id%></div>
第零步:等於源碼,只是把\n顯示出來
\n
    <% for ( var i = 0; i < items.length; i++ ) { %>    \n
        <% if( i%2 == 0) {%>\n
            <li><%=items[i].id%>:<%=items[i].name%></li>\n            
        <% } %> \n
    <% } %>\n
    <% print('數組長度' + items.length ); %>\n
    <div style='background:<%=color%>'><%=id%></div>\n
第一步: replace(/[\r\t\n]/g, " ")

去掉回車,換行,製表編程

<% for ( var i = 0; i < items.length; i++ ) { %>                 
        <% if( i%2 == 0) {%>                 
            <li><%=items[i].id%>:<%=items[i].name%></li>             
        <% } %>          
    <% } %>         
    <% print('數組長度' + items.length ); %>         
    <div style='background:<%=color%>'><%=id%></div>
第二步: split("<%").join("\t")

<%替換爲\tsegmentfault

\t for ( var i = 0; i < items.length; i++ ) { %>                 
        \t if( i%2 == 0) {%>                 
            <li>\t=items[i].id%>:\t=items[i].name%></li>             
        \t } %>          
    \t } %>         
    \t print('數組長度' + items.length ); %>         
    <div style='background:\t=color%>'>\t=id%></div>
第三步: replace(/((^|%>)[^\t]*)'/g, "$1\r")
替換須要保留的'爲\r, 主要是節點屬性操做
\t for ( var i = 0; i < items.length; i++ ) { %>                 
        \t if( i%2 == 0) {%>                 
            <li>\t=items[i].id%>:\t=items[i].name%></li>             
        \t } %>          
    \t } %>         
    \t print('數組長度' + items.length ); %>         
    <div style=\rbackground:\t=color%>\r>\t=id%></div>
第四步: replace(/\t=(.*?)%>/g, "',$1,'")
賦值部分替換,',$1,',實際是把賦值部分獨立出來,那麼push到這裏的時候,就會進行運算
\t for ( var i = 0; i < items.length; i++ ) { %>                 
        \t if( i%2 == 0) {%>                 
        <li>',items[i].id,':',items[i].name,'</li>             
        \t } %>          
    \t } %>         
    \t print('數組長度' + items.length ); %>         
    <div style=\rbackground:',color,'\r>',id,'</div>
第五步: split("\t").join("');")

剩下的\t,表明了js語句開始部分, js語句\t替換爲'); ,正是push的結束部分,正好完成push語句數組

'); for ( var i = 0; i < items.length; i++ ) { %>                 
        '); if( i%2 == 0) {%>                 
            <li>',items[i].id,':',items[i].name,'</li>              
        ');} %>          
    '); } %>         
    '); print('數組長度' + items.length ); %>         
    <div style=\rbackground:',color,'\r>',id,'</div>
第六步: split("%>").join("p.push('");

剩下的%>體表了js語句的結束,替換爲p.push('",開啓新的環節

'); for ( var i = 0; i < items.length; i++ ) { p.push('                 
        '); if( i%2 == 0) {p.push('                 
            <li>',items[i].id,':',items[i].name,'</li>             
        '); } p.push('          
    '); } p.push('         
    '); print('數組長度' + items.length ); p.push('         
    <div style=\rbackground:',color,'\r>',id,'</div>
第七部: split("\r").join("\'")

替換\r爲' , 恢復str.replace(/((^|%>)[^\t]*)'/g, "$1\r") 去掉的'

'); for ( var i = 0; i < items.length; i++ ) { p.push('
        '); if( i%2 == 0) {p.push('                 
            <li>',items[i].id,':',items[i].name,'</li>             
        '); } p.push('          
    '); } p.push('         
    '); print('數組長度' + items.length ); p.push('         
    <div style=\'background:',color,'\'>',id,'</div>
加上頭尾
var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('
    '); for ( var i = 0; i < items.length; i++ ) { p.push('
        '); if( i%2 == 0) {p.push('                 
            <li>',items[i].id,':',items[i].name,'</li>             
        '); } p.push('          
    '); } p.push('         
    '); print('數組長度' + items.length ); p.push('         
    <div style=\'background:',color,'\'>',id,'</div>  
    ');}return p.join('');

最後格式化一下

var p = [], print = function () { p.push.apply(p, arguments); }; with (obj) {
        p.push('    '); for (var i = 0; i < items.length; i++) {
            p.push('        '); if (i % 2 == 0) {
                p.push('            < li > ', items[i].id, ': ', items[i].name, '</li >            ');
            }
            p.push('      ');
        }
        p.push('      ');
        print('數組長度' + items.length); p.push('                    < div style =\'background:', color, '\'>', id, '</div>      ');
    }
    return p.join('');

split + join VS replace

源碼中你會發現,時而replace,時而split + join,你們都很清楚的能夠看出
split + join達到的效果是和replace徹底一致的。說到這裏,你們確定都很明白了,效率
我簡單作了個實驗,源碼以下,自行替換str的值,而後貼到控制檯執行,我測試的內容是打開百度,
查看源碼,把全部源碼賦值過來,而後執行。

var str = `
    blabla......................................
` + Math.random();
console.log('str length:' + str.length) 
console.log('a count:' + str.match(/a/g).length)

console.time('split-join-a')
str.split('a').join('_a_')
console.timeEnd('split-join-a')

console.time('replace-a')
str.replace(/a/g,'_a_')
console.timeEnd('replace-a')


console.log('window count:' + str.match(/window/g).length)
console.time('split-join-window')
str.split('window').join('_window_')
console.timeEnd('split-join-window')

console.time('replace-window')
str.replace(/window/g,'_window_')
console.timeEnd('replace-window')

執行結果
str length:114401 a count:4364 split-join-a: 4.521240234375ms replace-a: 13.24609375ms window count:29 split-join-window: 0.330078125ms replace-window: 0.297119140625ms
11萬個字符,
當匹配項是4000多得時候,執行時間相差比較大 ,
當匹配項是29的時候,知曉效率相差並不大,不少時候,replace比split+join還快
注意注意,這裏都是不帶正則查找,建議就是匹配項多得時候,用split +join嘍

能用否

這個模板如此簡單,能不能擔任重任。這是基於字符串模板,還有基於dom的模板,還有混合型的。
字符串模板的缺點拋開安全和性能,就是渲染後和頁面分離了,要想再操做,就須要本身再去定製了。
假如是僅僅是列表展示,是至關好的。

在線demo

MicroTemplating

一個對前端模板技術的全面總結
JavaScript 進階之深刻理解數據雙向綁定
模板引擎性能對比
最簡單的JavaScript模板引擎
有哪些好用的前端模板引擎?
JavaScript Micro-Templating

相關文章
相關標籤/搜索