浅谈模板引擎

模板原理

模板的诞生是为了将显示与数据分离,模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML代码。

模板技术并不是什么神秘技术,干的是拼接字符串的体力活。模板引擎就是利用正则表达式识别模板标识,并利用数据替换其中的标识符。比如:

Hello, <%= name%>

数据是{name: '木的树'},那么通过模板引擎解析后,我们希望得到Hello, 木的树。模板的前半部分是普通字符串,后半部分是模板标识,我们需要将其中的标识符替换为表达式。模板的渲染过程如下:

//字符串替换的思想
function tmpl(str, obj) {
    if (typeof str === 'string') {
        return str.replace(/<%=\s([^%>]+)\s%>/g, function() {
            var key = arguments[1];
            return obj[key];
        });
    }
}

var str = "Hello, <%= name%>";
var obj = {name: "Lzz"};

模板引擎

引擎核心

上面我们演示是简单的字符串替换,但对于模板引擎来说,要做的事情更复杂些。通常需要以下几个步骤:

  • 利用正则表达式分解出普通字符串和模板标识符,<%=%>的正则表达式为/<%=\s*([^%>]+)\s*%>/g.
  • 将模板标识符转换成普通的语言表达式
  • 生成待执行语句
  • 将数据填入执行,生成最终的字符串

Demo代码如下:

//编译的思想
function tmpl(str, obj) {
    if (typeof str === 'string') {
        var tm = str.replace(/<%=\s([^%>]+)\s%>/g, function() {
            var key = arguments[1];
            return "' + obj." + key; // 在函数字符串中利用'包裹正常字符串
        });

        tm = "return '" + tm; //"'Hello' + obj.name"
        var compile = new Function('obj', tm);
        return compile(obj);
    }
}

var str = "Hello, <%= name%>";
var obj = {name: "Lzz"}; // Hello, Lzz

模板编译

上述代码中有如下部分:

        tm = "return '" + tm; //"'Hello' + obj.name"
        var compile = new Function('obj', tm);

为了能够与数据一起执行生成字符串,我们需要将原始的模板字符串转换成一个函数对象。这个过程称为模板编译。模板编译使用了new Function(), 这里通过它创建了一个函数对象,语法如下:

new Function(arg1, arg2,..., functionbody)

Function()构造函数接受多个参数,最后一个参数作为函数体的内容,其之前的参数全部作为生成的新函数的参数。需要注意的是Function的参数全部是字符串类型,函数体部分对于字符串跟函数表达式一定要区分清楚,初学者往往在对函数体字符串中的普通字符串和表达式的拼接上犯错。一定要将函数体字符串和内部字符串正确拼接,如:

new Function('obj', "return 'Hello,' + obj.name")

或者对其中的字符换使用\"

new Function('obj', 'strip', "var tmp = \"\"; with(obj){ tmp = '';for(var i = 0; i < 3; i++){ tmp+='name is ' + strip(name) +' ';} tmp+=''; } return tmp;")

模板编译过程中每次都要利用Function重新生成一个函数,浪费CPU。为此我们可以将函数缓存起来,代码如下:

//模板预编译
var tmpl = (function(){
    var cache = {};
    return function(str, obj){
        if (!typeof str === 'string') {
            return;
        }
        var compile = cache[str];
        if (!cache[str]) {
            var tm = str.replace(/<%=\s([^%>]+)\s%>/g, function() {
                var key = arguments[1];
                return "' + obj." + key;
            });
            tm = "return '" + tm; //"'Hello' + obj.name"
            compile = new Function('obj', tm);
            cache[str] = compile;
        }
        return compile(obj); //预编译情况下应该返回compile函数
    }
}());
var str = "Hello, <%= name%>";
var obj = {name: "Lzz"};
tmpl(str, obj);

利用with

利用with我们可以不用把模板标识符转换成obj.name,只需要保持name标识符即可。

// 利用with使得变量自己寻找对象, 找不到的视为普通字符串
// 貌似return后面不能直接跟with
//模板预编译
var tmpl = (function(){
    var cache = {};
    return function(str, obj){
        if (!typeof str === 'string') {
            return;
        }
        var compile = cache[str];
        if (!cache[str]) {
            var tm = str.replace(/<%=\s([^%>]+)\s%>/g, function() {
                var key = arguments[1];
                return "' + " + key;
            });
            tm = "var tmp = \"\"; with(obj){ tmp = '" + tm + "; } return tmp;"; //"'Hello' + obj.name"
            compile = new Function('obj', tm);
            cache[str] = compile;
        }
        return compile(obj); //预编译情况下应该返回compile函数
    }
}());
var str = "Hello, <%= name%>";
var obj = {name: "LZZ"};
tmpl(str, obj);

XSS漏洞

如果上面的obj变成var obj = {name: "<script>alert(\"XSS\")</script>"};,那么最终生成的结果就会变成:

"Hello, <script>alert("XSS")</script>"

为此我们需要堵上这个漏洞,基本就是要将形成HTML标签的字符转换成安全的字符,这些字符通常是&, <, >, ", '。转换函数如下:

    var strip = function(html) {
        return String(html)
        .replace(/&/g, '&amp;')//&
        .replace(/</g, '&lt;')//左尖号
        .replace(/>/g, '&gt;')//右尖号
        .replace(/"/g, '&quot;')//双引号"
        .replace(/'/g, ''');//IE下不支持&apos;'
    }

这样下来,模板引擎应该变成这样:

var tmpl = (function(){
    var cache = {};
    var strip = function(html) {
        return String(html)
        .replace(/&/g, '&amp;')//&
        .replace(/</g, '&lt;')//左尖号
        .replace(/>/g, '&gt;')//右尖号
        .replace(/"/g, '&quot;')//双引号"
        .replace(/'/g, ''');//IE下不支持&apos;'
    }
    return function(str, obj){
        if (!typeof str === 'string') {
            return;
        }
        var compile = cache[str];
        if (!cache[str]) {
            //var tm = str.replace(/<%=\s([^%>]+)\s%>/g, function() {
            //    var key = arguments[1];
            //    return "' + strip(" + key + ")";
            //});
            var tm = str.replace(/<%=\s([^%>]+)\s%>/g, function() {
                var code = arguments[1];
                return "' + strip(" + code + ")"; //利用escape包裹code
            }).replace(/<%=\s([^%>]+)\s%>/g, function() {
                var key = arguments[1];
                return "' + " + key;
            });
            tm = "var tmp = \"\"; with(obj){ tmp = '" + tm + "; } return tmp;"; //"'Hello' + obj.name"
            compile = new Function('obj', 'strip', tm);
            cache[str] = compile;
        }
        return compile(obj, strip); //预编译情况下应该返回compile函数
    }
}());

var str = "<%= name%>";
var obj = {name: "<script>alert(\"XSS\")</script>"};
tmpl(str, obj);

这时候我们得到如下结果:

"&lt;script&gt;alert(&quot;XSS&quot;)&lt;/script&gt;"

模板逻辑

功能稍微强大的模板引擎,都允许在模板中添加一部分逻辑来控制页面的最终渲染。如:

var str = "<%for(var i = 0; i < 3; i++){%>name is <%= name%> <%}%>";

这里我们用<%%>代表逻辑代码<%=%>代表模板中需要替换的标识符。我们的模板代码变成了如下所示:

//模板逻辑
var tmpl = (function(){
    var cache = {};
    var strip = function(html) {
        return String(html)
        .replace(/&/g, '&amp;')//&
        .replace(/</g, '&lt;')//左尖号
        .replace(/>/g, '&gt;')//右尖号
        .replace(/"/g, '&quot;')//双引号"
        .replace(/'/g, ''');//IE下不支持&apos;'
    }
    return function(str, obj){debugger;
        if (!typeof str === 'string') {
            return;
        }
        var compile = cache[str];
        if (!cache[str]) {
            //var tm = str.replace(/<%=\s([^%>]+)\s%>/g, function() {
            //    var key = arguments[1];
            //    return "' + strip(" + key + ")";
            //});
            var tm = str.replace(/<%\s([^=][^%>])\s*%>/g, function() {
                var key = arguments[1];
                return "';" + key + " tmp+='"; // 逻辑代码需要一块块的拼接起来,为的是拼接成一段合理的函数字符串传递给new Function
            }).replace(/<%=\s([^%>]+)\s%>/g, function() {
                var code = arguments[1];
                return "' + strip(" + code + ") +'"; //利用escape包裹code ,加入模板逻辑时要注意,保证拼接成正确的函数字符串
            }).replace(/<%=\s([^%>]+)\s%>/g, function() {
                var key = arguments[1];
                return "' + " + key + "+ '";//加入模板逻辑时要注意,保证拼接成正确的函数字符串
            });debugger;
            tm = "var tmp = \"\"; with(obj){ tmp = '" + tm + "'; } return tmp;"; //"'Hello' + obj.name"
            compile = new Function('obj', 'strip', tm);
            cache[str] = compile;
        }
        return compile(obj, strip); //预编译情况下应该返回compile函数
    }
}());

var str = "<%for(var i = 0; i < 3; i++){%>name is <%= name%> <%}%>";
var obj = {name: "<script>alert(\"XSS\")</script>"};
tmpl(str, obj);

第一步,我们将模板中的逻辑表达式找出来,用的正则表达式是/<%\s([^=][^%>])\s*%>/g

str.replace(/<%\s([^=][^%>])\s*%>/g, function() {
                var key = arguments[1];
                return "';" + key + " tmp+='"; // 逻辑代码需要一块块的拼接起来,为的是拼接成一段合理的函数字符串传递给new Function
            })

注意在拼接时,为了防止函数字符串中的字符串没有闭合对表达式造成影响,我们在key前后都加了'保证其中的字符串闭合
第二步, 对可能存在的HTML标签进行转义

.replace(/<%=\s([^%>]+)\s%>/g, function() {
                var code = arguments[1];
                return "' + strip(" + code + ") +'"; //利用escape包裹code ,加入模板逻辑时要注意,保证拼接成正确的函数字符串
            })

同样需要注意前后的字符串闭合
第三步,像先前一样处理模板标识符

.replace(/<%=\s([^%>]+)\s%>/g, function() {
                var key = arguments[1];
                return "' + " + key + "+ '";//加入模板逻辑时要注意,保证拼接成正确的函数字符串
            })

仍然要注意其中的字符串闭合问题

模板引擎是一个系统的问题,复杂模板还支持模板嵌套,这里就不介绍了,希望此文能够抛砖引玉,让大火带来更好的干货!

时间: 2024-10-29 12:56:43

浅谈模板引擎的相关文章

浅谈Web中前后端模板引擎的使用

前言 这篇文章本来不打算写的,实话说楼主对前端模板的认识还处在非常初级的阶段,但是为了整个 源码解读系列 的完整性,在深入 Underscore _.template 方法源码后,觉得还是有必要记下此文,为了自己备忘也好,为了还没用上前端模板引擎的同学的入门也好.(熟悉模板引擎的可以帮楼主看看文中有没有 BUG ..) 后端 MVC 说起模板渲染,楼主首先接触的其实并不是前端模板引擎,而是后端.后端 MVC 模式中,一般从 Model 层中读取数据,然后将数据传到 View 层渲染(渲染成 HT

CMS模板引擎:XHtmlAction

前言: 先说说大伙关心的工作上的事,在上家公司任了一个多月的技术经理后,和公司中止了合作关系. 主要原因在于一开始的待遇没谈的太清楚:  1:没有合同,没有公积金,连社保也没交. 2:工资的30%变成了绩效(对我还实行特例,按季度或按项目发,而且绩效只有按期完成(发)与没完成(不发)) 3:税后的问题,要自己去弄发票来填. 只能说缘来的太快,份走的也太快.   对于工作上的事,一个多月的时间,从需求文档到概要文档到详细文档,到产品原型到系统架构,基本上已经走完了. 项目成员也招聘完成,开发的按我

浅谈Jquery核心函数_jquery

      在Jquery中,所有的DOM对象都将封装成Jquery对象,而且只有Jquery对象才能使用Jquery方法或者属性来执行相应的操作. 所以Jquery提供了一个可以将DOM对象封装成Jquery对象的函数,就是Jquery核心函数jquery(),也称为工厂函数. jquery核心函数有7个重载,分别如下: jquery()  该函数返回一个空的jquery对象. jquery(elements)  该函数将一个或多个DOM元素转化为Jquery对象(或jquery集合) jqu

浅谈javascript中自定义模版_javascript技巧

/** * Created by Administrator on 15-1-19. */ function functionUtil() { } functionUtil = { //某个DOM节点是否有某个属性 hasAttr: function (el, name) { var attr = el.getAttributeNode && el.getAttributeNode(name); return attr ? attr.specified : false }, //根据cla

浅谈关于JavaScript API设计的一些建议和准则

  这篇文章主要介绍了浅谈关于JavaScript API设计的一些建议和准则,文中列举了许多知名的JS API进行辅助说明,极力推荐!需要的朋友可以参考下 设计是一个很普遍的概念,一般是可以理解为为即将做的某件事先形成一个计划或框架. (牛津英语词典)中,设计是一种将艺术,体系,硬件或者更多的东西编织到一块的主线.软件设计,特别是作为软件设计的次类的API设计,也是一样的.但是API设计常常很少关注软件发展,因为为其他程序员写代码的重要性要次于应用UI设计和最终用户体验. 但是API设计,作为

浅谈网站内部优化

酝酿了很久今天小c就按个人经验说说站内优化的一些建议.大家都知道内容为王外链为皇的道理,何为内容为王,原创?当然,内容为王的真正含义版本不一,原创是基础这个是无可厚非的,下面小c谈谈如何做好站内优化吧. 1.合理的url路径的设计 为什么我要把它放在第一位来说呢,因为小c觉得它是最重要的.浏览网站需要什么,路径对吧,合理的url设计不仅美观更加方便搜索引擎的收录,大大 提高搜索引擎的友好度.如果一开始url设计不合理,不仅影响搜索引擎对网站的不友好,对今后的优化工作会有非常大的影响,所有把url

浅谈网站经营管理二、三事

浅谈网站经营管理二.三事 建置好一个网站,便要正式迈向经营的路程.其实网站虽然本身的功能使用设计非常重要,但经营的好坏,才是一个网站是否能够生存的关键. 推销你的网站 一个网站做的再怎么好,若是没有人知道网站的存在,那么一切都是枉然,因此将网站广为告知是网站经营的第一个动作. 在传统的营销观念里,谈到营销第一个直觉就是要花钱.无可讳言的,在预算许可的前提下,透过一些传统营销媒体的运作,例如电视广告.户外媒体.宣传造势活动等,是提升网站知名度.增加阅览率最直接的方法,而这些方式在前几年网络投资热络

PHP模板引擎Smarty介绍

模板 用PHP实现MVC开发模式的逻辑层和表示层有多种模板引擎可供选择,但是官方引擎SMARTY诞生后,选择就有了变化.它的理念和实现都是相当"前卫"的.本文主要讨论SMARTY之于其他模板引擎的不同特点,简要介绍了该引擎的安装及使用,并用一个小的测试案例对比了SMARTY和PHPLIB template的速度和易用性. 一.MVC需要模板 MVC最早是在SmallTalk语言的开发过程中总结出的一种设计模式,MVC分别代表了"模型"."视图"和

模板引擎SMARTY

模板 用PHP实现MVC开发模式的逻辑层和表示层有多种模板引擎可供选择,但是官方引擎SMARTY诞生后,选择就有了变化.它的理念和实现都是相当"前卫"的.本文主要讨论SMARTY之于其他模板引擎的不同特点,简要介绍了该引擎的安装及使用,并用一个小的测试案例对比了SMARTY和PHPLIB template的速度和易用性.一.MVC需要模板MVC最早是在SmallTalk语言的开发过程中总结出的一种设计模式,MVC分别代表了"模型"."视图"和&q