javascript模板引擎和完成道理

模板简介

模板平常是指嵌入了某种动态编程言语代码的文本,数据和模板经由历程某种情势的连系,可以变化出差异的结果。模板平常用来定义显现的情势,可以使得数据展示更加雄厚,而且轻易保护。比方,下面是一个模板的例子:

<ul>
    <% for(var i in items){ %>
        <li class='<%= items[i].status %>'><%= items[i].text %></li>
    <% } %>
</ul>

假如有以下items数据:

items:[
    { text: 'text1' ,status:'done' },
    { text: 'text2' ,status:'pending' },
    { text: 'text3' ,status:'pending' },
    { text: 'text4' ,status:'processing' }
]

经由历程某种体式格局的连系,可以发生下面的Html代码:

<ul>
    <li class='done'>text1<li>
    <li class='pending'>text2<li>
    <li class='pending'>text3<li>
    <li class='processing'>text4<li>
</ul>

假如不运用模板,想要到达一样的结果,行将上面的数据展示成结果的模样,须要像下面如许做:

var temp = '<ul>';
for(var i in items){
    temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
}
temp += '</ul>';

可以看出运用模板有以下优点:

  • 简化了html的誊写
  • 经由历程编程元素(比方轮回和前提分支),对数据的展示更具有掌握的才能
  • 分离了数据与展示,使得展示的逻辑和结果更容易保护

模板引擎

经由历程剖析模板,将数据和模板连系在一起输出末了的结果的顺序称为模板引擎,模板有很多种,相对应的模板引擎也有很多种。一种比较陈旧的模板称为ERB,在很多的web框架中被采纳,比方:ASP.NETRails … 上面的例子就是ERB的例子。在ERB中两个中心的观点:evaluateinterpolate。表面上evaluate是指包括在<% %>中的部份,interpolate是指包括在<%= %>中的部份。从模板引擎的角度,evaluate中的部份不会直接输出到结果中,平常用于历程掌握;而interpolate中的部份将直接输出到结果中。

从模板引擎的完成上看,须要依靠编程言语的动态编译或许动态诠释的特征,以简化完成和进步机能。比方:ASP.NET应用.NET的动态编译,将模板编译成动态的类,并应用反射动态实行类中的代码。这类完成实际上是比较复杂的,由于C#是一门静态的编程言语,然则运用javascript可以应用Function,以少少的代码完成一个浅易的模板引擎。本文就来完成一个浅易的ERB模板引擎,以展示javascript的壮大的地方。

模板文本转化

针对上面的例子,回忆一下运用模板和不运用模板的差异:

模板写法:

<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>';

仔细观察,实际上这两种要领非常“类似”,可以找到某种意义上的一一对应。假如可以将模板的文本变成代码实行,那末就可以完成模板转化。在转化历程当中有两个准绳:

  1. 碰到一般的文本直接当做字符串拼接
  2. 碰到interpolate(即<%= %>),将个中的内容当做变量拼接在字符串中
  3. 碰到evaluate(即<% %>),直接当做代码

将上面的例子根据上述准绳举行变更,再增加一个总的函数:

var template = function(items){
    var temp = '';
    //最先变更
    temp += '<ul>';
    for(var i in items){
        temp += "<li class='" + items[i].status + "'>" + items[i].text + "</li>";
    }
    temp += '</ul>';
}

末了实行这个函数,传入数据参数即可:

var result = template(items);

javascript动态函数

可见上面的转化逻辑实在非常简朴,然则症结的题目是,模板是变化的,这意味着天生的顺序代码也必需是在运行时天生并实行的。幸亏javascript有很多动态特征,个中一个壮大的特征就是Function
我们平常运用function症结字在js中声明函数,很罕用Function。在js中function是字面语法,js的运行时会将字面的function转化成Function对象,所以实际上Function供应了更加底层和天真的机制。

用 Function 类直接建立函数的语法以下:

var function_name = new Function(arg1, arg2, ..., argN, function_body)

比方:

//建立动态函数    
var sayHi = new Function("sName", "sMessage", "alert(\"Hello \" + sName + sMessage);");
//实行    
sayHi('Hello','World');

函数体和参数都可以经由历程字符串来建立!So cool!有了这个特征,可以将模板文本转化成函数体的字符串,如许就可以建立动态的函数来动态的挪用了。

完成思绪

起首应用正则式来形貌interpolateevaluate,括号用来分组捕捉:

var interpolate_reg = /<%=([\s\S]+?)%>/g;
var evaluate_reg = /<%([\s\S]+?)%>/g;

为了对全部模板举行一连的婚配将这两个正则式兼并在一起,然则注重,一切可以婚配interpolate的字符串都能婚配evaluate,所以interpolate须要有较高的优先级:

var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g

设想一个函数用于转化模板,输入参数为模板文本字串和数据对象

var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){ ... }

运用replace要领,举行正则的婚配和“替代”,实际上我们的目标不是要替代interpolateevaluate,而是在婚配的历程当中构建出“要领体”:

var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>/g
//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){
    var index = 0;//纪录当前扫描到哪里了
    var function_body = "var temp = '';";
    function_body += "temp += '";
    text.replace(matcher,function(match,interpolate,evaluate,offset){
        //找到第一个婚配后,将前面部份作为一般字符串拼接的表达式
        function_body += text.slice(index,offset);

        //假如是<% ... %>直接作为代码片断,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;";
}

至此,function_body虽然是个字符串,但内里的内容实际上是一段函数代码,可以用这个变量来动态建立一个函数对象,并经由历程data参数挪用:

var render = new Function('obj', function_body);
return render(data);

如许render就是一个要领,可以挪用,要领内部的代码由模板的内容组织,然则大抵的框架应该是如许的:

function render(obj){
    var temp = '';
    temp += ...
    ...
    return temp;
}

注重到,要领的形参是obj,所以模板内部援用的变量应该是obj

<script id='template' type='javascript/template'>
    <ul>
        <% for(var i in obj){ %>
            <li class="<%= obj[i].status %>"><%= obj[i].text %></li>
        <% } %>
    </ul>
</script>

看似到这里就OK了,然则有个必需处置惩罚的题目。模板文本中能够包括\r \n \u2028 \u2029等字符,这些字符假如出现在代码中,会失足,比以下面的代码是毛病的:

temp += '
        <ul>
    ' + ... ;

我们愿望看到的应该是如许的代码:

temp += '\n \t\t<ul>\n' + ...;

如许须要把\n前面的\转义成\\即可,终究变成字面的\\n

别的,另有一个题目是,上面的代码没法将末了一个evaluate或许interpolate背面的部份拼接进来,处置惩罚这个题目的方法也很简朴,只须要在正则式中增加一个行尾的婚配即可:

var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g;

相对完全的代码

var matcher = /<%=([\s\S]+?)%>|<%([\s\S]+?)%>|$/g

//模板文本中的特别字符转义处置惩罚
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
var escapes = {
    "'":      "'",
    '\\':     '\\',
    '\r':     'r',
    '\n':     'n',
    '\t':     't',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

//text: 传入的模板文本字串
//data: 数据对象
var template = function(text,data){
    var index = 0;//纪录当前扫描到哪里了
    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('obj', function_body);
    return render(data);
}

挪用代码可所以如许:

<script id='template' type='javascript/template'>
    <ul>
        <% for(var i in obj){ %>
            <li class="<%= obj[i].status %>"><%= obj[i].text %></li>
        <% } %>
    </ul>
</script>

...

var text = document.getElementById('template').innerHTML;
var items = [
    { text: 'text1' ,status:'done' },
    { text: 'text2' ,status:'pending' },
    { text: 'text3' ,status:'pending' },
    { text: 'text4' ,status:'processing' }
];
console.log(template(text,items));

可见,我们只用了很少的代码就完成了一个浅易的模板。

遗留的题目

另有几个细节的题目须要注重:

  1. 由于<%或许%>都是模板的边境字符,假如模板须要输出<%或许%>,那末须要设想转义的方法
  2. 假如数据对象中包括有null,明显不愿望末了输出'null',所以须要在function_body的代码中斟酌null的状况
  3. 在模板中每次运用obj的形参援用数据,能够不太轻易,可以在function_body增加with(obj||{}){...},如许模板中可以直接运用obj的属性
  4. 可以设想将render返回出去,而不是返回转化的结果,如许外部可以缓存天生的函数,以进步机能
    原文作者:P_Chou水冗
    原文地址: https://segmentfault.com/a/1190000000432600
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞