模板引擎原理&实例

本文引至: 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

new Funciton()

通常咱们定义一个函数, 最快捷的办法就是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

replace

由于获取的是一个字符串.因此,咱们一般须要使用正则来进行简单的匹配. 最早想到的就是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的方法.

push方式-John

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). 到这里,模板引擎这块已经到了一个峰值了. 后面的难点就是如何进行模板的更新和替换了.

相关文章
相关标签/搜索