继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

咱一起来手撸一个模板引擎吧。

dorseyCh
关注TA
已关注
手记 50
粉丝 1.3万
获赞 1519

    jQuery时代的时候,相信有很多人用过template,(也就是模板引擎),那这样的一个模板引擎,大概长什么样?它的实现机理又是怎样的呢?咱自己手动写一个模板引擎实现,一起看看这个方便实用的东西内部是如何工作的吧。

    传统字符串拼接的代码:

https://img2.mukewang.com/5bd9234e00011b0305140215.jpg

    用模板引擎的代码:

https://img.mukewang.com/5bd922720001f2e105580213.jpg

    模板引擎直观,易懂,易拷贝,易修改,动态的特点,让它在当前主流的页面渲染模式中占着最主要的比重,从jQuery时代的传统模板(上面,写在单独的script标签),到MVVM框架的常见模板(直接在标签里写各种模板),以及ES6/7后出现的`${}`模板字符串,都是各种不同类型的模板引擎。因为现在与最初的前端写页面,后台绑数据不同(难改,改动量大,难理解),后台是写接口,前端是调接口,前后端联调,业务改变了,前端稍作修改即可(后台基本可以不动,除非需求改动大),而这种模式下,前端首先是不知道会返回多少数据量,只知道字段名,所以前端首先都是根据字段名写好各个模块的模板,再链接上后台的数据,而模板引擎是相当方便也相当好用的一种方式。代码改动起来也简单。比传统的字符串拼接要简单有用得多。

    传统的模板引擎呢,也难免存在一些小缺陷,因为有数据才有内容,所以的话大多需要同步工作,什么意思呢?就是数据要先拿到,再加载模板。现在新的MVVM模板比如vue都是双向的,模板即数据,view即data,相对来说性能更好,体验也更佳。

    而本次我们要实现的呢,大概是这样的:

https://img3.mukewang.com/5bd96f1300011cd911830727.jpg

    最终呢,页面上输出:

https://img.mukewang.com/5bd96f4a000114c216580473.jpg

    要实现这个,我们需要先明白模板引擎的工作原理,它的原理很简单,利用符合某种正则匹配规则(自己定义,我这里用的是{%%}和{%=%}这个,带“=”的代表是值,不带的则是一个js函数表达式。)的字符串,将之替换成js代码,并在写入的时候执行。

    那怎么做呢?我们一步步来看:

    首先是要匹配对应的字符串吧?用正则去匹配带{%%}和{%=%}标识符的字符串。

https://img.mukewang.com/5bd9715c0001d0bb15010476.jpg

    咱们match一下,可以看到我们所有带有所需标识符的各个片段都被我们搜索出来了。

    接下来呢?

    看下这段代码:

https://img4.mukewang.com/5bd91d4d00016ad113490307.jpg

var data = [{name: "dorsey",age: 25,sex: "男"},{name: "sen",age: 25,sex: "女"}];
var tmpl = new Function ("data","for (var i = 0; i < data.length; i ++) { console.log(\"名字: \" + data[i].name + \" 年龄: \" + data[i].age + \" 性别: \" + data[i].sex); }");
tmpl(data);

再看看输出:

https://img2.mukewang.com/5bd91d8b00013dbe09300049.jpg

    是不是很神奇?因为我写的是一段字符串啊。console.log()那些代码都是字符串啊,居然还能执行?这是因为js本身构造函数时就具备了这个能力,这样跟我们想要的结果好像很接近很接近了,对吧?

    实际上我们要做的,就是取出逻辑部分(js代码执行部分),跟字符串部分,装进new Function,这样就基本实现了我们想要的结果。js是一门动态语言,用在这上是最方便也最容易的。(静态语言类似于C#据说要先将模板转成动态类,再用反射动态执行类中的代码,比较复杂)

    现在我们完成了哪些呢?

    第一:拿到要修改成js逻辑的字符串片段

    第二:知道怎么把字符串转成js逻辑代码

    那还差什么呢?注意到new function内部的字符串是一整段的,而我们现在有的还只是一小段一小段的片段,最重要的一步来了,拼接,拼接成一个特殊的字符串,看看这个字符串长什么样哈,你会发现,哦。。。原来是这样。。。

https://img.mukewang.com/5bd9741e000185e617000385.jpg

    那每一段都有了,要拼凑成一个这样的代码不是很简单了吗?确实不难,注意下细节就OK了。

https://img1.mukewang.com/5bd97846000113ae11300557.jpg

    这里的话replace是替换,当正则匹配有多条规则时,其回调返回的参数这里做下小说明:

    match:是匹配出来的字符片段

    rank1:正则匹配当有多条规则时,有个优先级,从左到右一个个匹配(“|”根据或符号),是第一个级别的带匹配符号{%=%}的整段字符串。

    content1:则是rank1对应的不带匹配符号的内部内容。后续rank2,content2也是一样

    offset是偏移量,对应的是这个片段在当前整个字符串的位置。

    另外的话呢,由于我们是把字符串转成js代码,会存在着XSS漏洞攻击的可能,所以把代码转成安全的符号,大概这样:

https://img1.mukewang.com/5bd978c00001667607850179.jpg

    (这部分还没嵌进去)

    最后呢,贴一下代码吧:(代码贴上去不会换行,勉强贴成这样子了)

    html部分

<!DOCTYPE html>

<html>

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <meta http-equiv="X-UA-Compatible" content="ie=edge">

    <title>模板引擎模块测试页</title>

    <style>

        html,body,div,ul,li,p{margin:0;padding:0;}

        ul{list-style:none}

    </style>

</head>

<body>

    

    <div class="test" id="test">

    </div>

    <script type="text/dc-tmpl" id = "dorsey">

        <ul>

            <li>{%= d.name %}</li>

            <li>{%= d.age %}</li>

            <li>{%= d.sex %}</li>

        </ul>

        <p>他有伙伴:</p>

        <ul>

            {% for(var i = 0; i < d.partner.length; i ++) { %}

                <li>{%= d.partner[i].name %}</li>

            {% } %}

        </ul>

    </script>

    

    <script type="module">

        

        import _Tmpl from "../../js/dc_plugin/dc_tmpl.js";

        let dorsey = {

            name : "dorsey",

            age : 24,

            sex : "男",

            partner : [

                {

                    name: "xiaoming"

                },

                {

                    name: "xiaohua"

                }

            ]

        };

        document.querySelector(".test").innerHTML = _Tmpl.tmpl("dorsey",dorsey);

        

    </script>

</body>

</html>

js部分

const _Tmpl = {


tmpl (tmplID,d) {


let html = document.getElementById(tmplID).innerHTML,

reg = /(\{%=([\s\S]+?)%\})|(\{%([\s\S]+?)%\})/g;


let tran = {

"\n": "\\n",

"\r": "\\r",

"\t": "\\t"

}

let str_fn = "let tmpl = \"\";tmpl += \"";

let index = 0;

html.replace(reg, (match, rank1, content1, rank2, content2, offset) => {

str_fn += html.slice(index,offset).replace(/\\|'|\r|\n|\t/g, function(match) { return tran[match];});//换行符,table,空格转义

if(rank1){

str_fn += "\" + " + content1 + " + \"";

}

if(rank2){

str_fn += "\";" + content2 + "tmpl += \"";

}

index = offset + match.length;

});

str_fn += "\";return tmpl;"

let template = new Function("d",str_fn);

return template(d);

},

/*为避免受到XSS漏洞攻击,将html字符换换成安全的转义字符*/

xssRsp (html) {

return String(html)

.replace(/&/g,"&amp;")

.replace(/</g,"&lt;")

.replace(/>/g,"&gt;")

.replace(/"/g,"&quot;")

.replace(/'/g,"&#039;");

},

test () {

}

}

export default _Tmpl;

    现在暂时没push上去。

    这样一个简单的模板引擎就出来了= =。是不是很cool?





打开App,阅读手记
10人推荐
发表评论
随时随地看视频慕课网APP