最轻量级前端模板Micro-Templating, 源码解析

简介: 关于模板,写页面的人们其实一直在用,asp.net , jsp , php, nodejs等等都有他的存在,当然那是服务端的模板。前端模板,作为前端人员肯定是多少有接触的,Handlebars.js,JsRender,Dust.js,Mustache.js,Underscore templates,Angularjs,Vuejs,reactjs到处都离不开模板的影子。

关于模板



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


相关文章
|
存储 前端开发 安全
前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
本文全面解析前端三种数据存储方式:Cookie、LocalStorage与SessionStorage。涵盖其定义、使用方法、生命周期、优缺点及典型应用场景,帮助开发者根据登录状态、用户偏好、会话控制等需求,选择合适的存储方案,提升Web应用的性能与安全性。(238字)
580 0
|
7月前
|
JavaScript 前端开发 Java
制造业ERP源码,工厂ERP管理系统,前端框架:Vue,后端框架:SpringBoot
这是一套基于SpringBoot+Vue技术栈开发的ERP企业管理系统,采用Java语言与vscode工具。系统涵盖采购/销售、出入库、生产、品质管理等功能,整合客户与供应商数据,支持在线协同和业务全流程管控。同时提供主数据管理、权限控制、工作流审批、报表自定义及打印、在线报表开发和自定义表单功能,助力企业实现高效自动化管理,并通过UniAPP实现移动端支持,满足多场景应用需求。
694 1
|
6月前
|
Web App开发 前端开发 JavaScript
前端性能优化利器:图片懒加载实战解析
前端性能优化利器:图片懒加载实战解析
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
534 70
|
5月前
|
人工智能 JSON 前端开发
如何解决后端Agent和前端UI之间的交互问题?——解析AG-UI协议的神奇作用
三桥君指出AG-UI协议通过SSE技术实现智能体与前端UI的标准化交互,解决流式传输、实时进度显示、数据同步等开发痛点。其核心功能包括结构化事件流、多Agent任务交接和用户中断处理,具有"一次开发到处兼容"、"UI灵活可扩展"等优势。智能体专家三桥君认为协议将AI应用从聊天工具升级为实用软件,适用于代码生成、多步骤工作流等场景,显著提升开发效率和用户体验。
1289 0
|
7月前
|
存储 前端开发 JavaScript
|
8月前
|
存储 前端开发 JavaScript
调用DeepSeek API增强版纯前端实现方案,支持文件上传和内容解析功能
本方案基于DeepSeek API增强版,提供纯前端实现的文件上传与内容解析功能。通过HTML和JavaScript,用户可选择文件并调用API完成上传及解析操作。方案支持多种文件格式(如PDF、TXT、DOCX),具备简化架构、提高响应速度和增强安全性等优势。示例代码展示了文件上传、内容解析及结果展示的完整流程,适合快速构建高效Web应用。开发者可根据需求扩展功能,满足多样化场景要求。
2691 64
|
6月前
|
JSON 前端开发 安全
前端开发中常用的鉴权方式解析与实践要点
本文深入探讨了前端开发中常用的鉴权方式,包括HTTP基本鉴权、Session-Cookie鉴权、Token验证、JWT(JSON Web Tokens)、单点登录(SSO)和OAuth等。文章首先明确了认证、授权、鉴权和权限控制的概念及关系,随后详细解析每种鉴权方式的工作原理、优缺点及适用场景。例如,HTTP基本鉴权简单但安全性低,适合内部网络;Session-Cookie鉴权易受CSRF攻击,适用于同域Web应用;Token和JWT无状态且扩展性好,适合分布式系统;SSO提升用户体验,适用于多系统统一登录;OAuth安全方便,适合第三方授权接入。
524 2
|
9月前
|
前端开发 算法 NoSQL
前端uin后端php社交软件源码,快速构建属于你的交友平台
这是一款功能全面的社交软件解决方案,覆盖多种场景需求。支持即时通讯(一对一聊天、群聊、文件传输、语音/视频通话)、内容动态(发布、点赞、评论)以及红包模块(接入支付宝、微信等第三方支付)。系统采用前后端分离架构,前端基于 UniApp,后端使用 PHP 框架(如 Laravel/Symfony),配合 MySQL/Redis 和自建 Socket 服务实现高效实时通信。提供用户认证(JWT 集成)、智能匹配算法等功能,助力快速上线,显著节约开发成本。
309 1
前端uin后端php社交软件源码,快速构建属于你的交友平台
|
9月前
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
389 4

热门文章

最新文章

  • 1
    前端如何存储数据:Cookie、LocalStorage 与 SessionStorage 全面解析
    580
  • 2
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(九):强势分析Animation动画各类参数;从播放时间、播放方式、播放次数、播放方向、播放状态等多个方面,完全了解CSS3 Animation
    238
  • 3
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(八):学习transition过渡属性;本文学习property模拟、duration过渡时间指定、delay时间延迟 等多个参数
    226
  • 4
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(七):学习ransform属性;本文学习 rotate旋转、scale缩放、skew扭曲、tanslate移动、matrix矩阵 多个参数
    164
  • 5
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(六):全方面分析css的Flex布局,从纵、横两个坐标开始进行居中、两端等元素分布模式;刨析元素间隔、排序模式等
    275
  • 6
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(五):背景属性;float浮动和position定位;详细分析相对、绝对、固定三种定位方式;使用浮动并清除浮动副作用
    408
  • 7
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(四):元素盒子模型;详细分析边框属性、盒子外边距
    177
  • 8
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(三):元素继承关系、层叠样式规则、字体属性、文本属性;针对字体和文本作样式修改
    112
  • 9
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(二):CSS伪类:UI伪类、结构化伪类;通过伪类获得子元素的第n个元素;创建一个伪元素展示在页面中;获得最后一个元素;处理聚焦元素的样式
    191
  • 10
    【CSS】前端三大件之一,如何学好?从基本用法开始吧!(一):CSS发展史;CSS样式表的引入;CSS选择器使用,附带案例介绍
    261
  • 推荐镜像

    更多
  • DNS