本文引至: 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
通常咱们定义一个函数, 最快捷的办法就是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
由于获取的是一个字符串.因此,咱们一般须要使用正则来进行简单的匹配. 最早想到的就是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的方法.
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). 到这里,模板引擎这块已经到了一个峰值了. 后面的难点就是如何进行模板的更新和替换了.