最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 最轻量级前端模板Micro-Templating, 源码解析

    正文概述 掘金(云的世界)   2021-08-21   457

    这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战。

    关于模板

    关于模板,写页面的人们其实一直在用,asp.net , jsp , php, nodejs等等都有他的存在,当然那是服务端的模板。

    前端模板,作为前端人员肯定是多少有接触的,Handlebars.js,JsRender,Dust.js,Mustache.js,Underscore templates,Angularjs,Vuejs,reactjs到处都离不开模板的影子。

    Micro-Templating解析在线测试

    MicroTemplating

    Micro-Templating模板

    本文主要是分析一下jQuery的创始人的Micro-Templating,麻雀虽小缺五张俱全。

    到这里可别笑,说用什么jQuery的,jQuery的思想绝对是异常的优秀,霸榜十多年, React也不过是最近两年才赶超。

    先贴出作者的源码:

    // 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参数,返回的执行函数,可以延迟使用,到处使用。

    print

    调试的日志输出。

    重点在于2和4,在这之前,先看看print,这个print申请在函数顶部,就表示在js语句的时候是可以调用呢,怎么调用呢,看看示例,至于作用么,当然是debug啊

        <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和备注,我调整一下原理结构

    (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作为特定条件下'的占位符。

    逐步分析

    我们接下来按照正则替换一步异步来分析

    模板源码

        <% 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")

    <%替换为\t

        \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')
    
    

    执行结果

    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的模板,还有混合型的。 字符串模板的缺点抛开安全和性能,就是渲染后和页面分离了,要想再操作,就需要自己再去定制了。 假如是仅仅是列表展现,是相当好的。

    写在最后

    如果你觉得不错,你的一赞一评就是我前行的最大动力。

    技术交流群请到 这里来。 或者添加我的微信 dirge-cloud,一起学习。


    起源地下载网 » 最轻量级前端模板Micro-Templating, 源码解析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元