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

关于前端模板的分类,我会在单独的博客来和大家一起学习。

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

先贴出作者的源码:

// 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的模板,还有混合型的。

字符串模板的缺点抛开安全和性能,就是渲染后和页面分离了,要想再操作,就需要自己再去定制了。

假如是仅仅是列表展现,是相当好的。

在线demo

MicroTemplating

一个对前端模板技术的全面总结

JavaScript 进阶之深入理解数据双向绑定

模板引擎性能对比

最简单的JavaScript模板引擎

有哪些好用的前端模板引擎?

JavaScript Micro-Templating

Micro Templating源码分析的更多相关文章

  1. Jvm(jdk8)源码分析1-java命令启动流程详解

    JDK8加载源码分析 1.概述 现在大多数互联网公司都是使用java技术体系搭建自己的系统,所以对java开发工程师以及java系统架构师的需求非常的多,虽然普遍的要求都是需要熟悉各种java开发框架 ...

  2. 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 百篇博客分析OpenHarmony源码 | v55.01

    百篇博客系列篇.本篇为: v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程 ...

  3. 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 百篇博客分析OpenHarmony源码 | v51.04

    百篇博客系列篇.本篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51.c.h.o 加载运行相关篇为: v51.xx 鸿蒙内核源码分析(ELF格式篇) | ...

  4. qiankun 2.x 运行时沙箱 源码分析

    简介 从源码层面详细讲解了 qiankun 框架中的 JS 沙箱 和 样式沙箱的实现原理. 序言 沙箱 这个词想必大家应该不陌生,即使陌生,读完这篇文章也就不那么陌生了 沙箱 (Sandboxie) ...

  5. 【图解源码】Zookeeper3.7源码分析,包含服务启动流程源码、网络通信源码、RequestProcessor处理请求源码

    Zookeeper3.7源码剖析 能力目标 能基于Maven导入最新版Zookeeper源码 能说出Zookeeper单机启动流程 理解Zookeeper默认通信中4个线程的作用 掌握Zookeepe ...

  6. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  7. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  8. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  9. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

随机推荐

  1. 《跟我学IDEA》二、配置maven、git、tomcat

    上一篇博文我们讲解了如何去下载并安装一个idea,在这里我们推荐的是zip的解压版,另外我们配置的一些编码和默认的jdk等.今天我们来学习配置maven.git.tomcat等.还是那句话,工欲善其事 ...

  2. php的过滤器功能

    1.简介 PHP 过滤器用于验证和过滤来自非安全来源的数据,比如用户的输入. 1.1 外部数据: 来自表单的输入数据 Cookies Web services data 服务器变量 数据库查询结果 1 ...

  3. 关于我之前写的修改Windows系统Dos下显示用用户名的名字再测试

    最近看到蛮多网友反映,自己修改Dos下用户名后出现了很多的问题--今天抽了时间,再次修改测试... ================= Win10下C:\Users\John以账户名称命名的系统文件夹 ...

  4. Unix:关于一个file在file system和disk中占用空间

    參考文献: Harley Hahns:Guide to Unix and Linux. Chap 24 -->首先要有的关键概念:the amount of "disk space&q ...

  5. 技术债务管理以及Firefox/Chromium的债务评价

    如今的软件开发是在遍地敏捷,人人讲唯快不破的时代,哪有人有时间思考代码质量,设计的质量? 哪个又不是从一堆代码中杀出血路来实现还有一个功能?一个产品都存活不了几年,何必考虑什么可维护性? 我们追求进度 ...

  6. 指尖上的电商---(4).net开发solr

    这一节我们看下如何把查询数据放到server端存储,这里我们须要使用client工具来操作与服务端数据打交道,网上有好多基于.NET开发的SOLRclient,我们这里选择easynet.solr,非 ...

  7. 字符串函数---atof()函数具体解释及实现(完整版)

    atof()函数 atof():double atof(const char *str ); 功 能: 把字符串转换成浮点数 str:要转换的字符串. 返回值:每一个函数返回 double 值.此值由 ...

  8. SDL2源码分析2:窗体(SDL_Window)

    ===================================================== SDL源码分析系列文章列表: SDL2源码分析1:初始化(SDL_Init()) SDL2源 ...

  9. C#设计模式之二十一职责链模式(Chain of Responsibility Pattern)【行为型】

    一.引言   今天我们开始讲"行为型"设计模式的第八个模式,该模式是[职责链模式],英文名称是:Chain of Responsibility Pattern.让我们看看现实生活中 ...

  10. MPSOC之2——ubuntu环境配置及petalinux安装

    MPSOC的linux开发需要使用petalinux,选择Ubuntu操作系统. 1.Ubuntu 1.1. Ubuntu安装 版本16.04.03 vmare版本:12.0 安装时注意选择" ...