jQuery时代的时候,相信有很多人用过template,(也就是模板引擎),那这样的一个模板引擎,大概长什么样?它的实现机理又是怎样的呢?咱自己手动写一个模板引擎实现,一起看看这个方便实用的东西内部是如何工作的吧。
传统字符串拼接的代码:
用模板引擎的代码:
模板引擎直观,易懂,易拷贝,易修改,动态的特点,让它在当前主流的页面渲染模式中占着最主要的比重,从jQuery时代的传统模板(上面,写在单独的script标签),到MVVM框架的常见模板(直接在标签里写各种模板),以及ES6/7后出现的`${}`模板字符串,都是各种不同类型的模板引擎。因为现在与最初的前端写页面,后台绑数据不同(难改,改动量大,难理解),后台是写接口,前端是调接口,前后端联调,业务改变了,前端稍作修改即可(后台基本可以不动,除非需求改动大),而这种模式下,前端首先是不知道会返回多少数据量,只知道字段名,所以前端首先都是根据字段名写好各个模块的模板,再链接上后台的数据,而模板引擎是相当方便也相当好用的一种方式。代码改动起来也简单。比传统的字符串拼接要简单有用得多。
传统的模板引擎呢,也难免存在一些小缺陷,因为有数据才有内容,所以的话大多需要同步工作,什么意思呢?就是数据要先拿到,再加载模板。现在新的MVVM模板比如vue都是双向的,模板即数据,view即data,相对来说性能更好,体验也更佳。
而本次我们要实现的呢,大概是这样的:
最终呢,页面上输出:
要实现这个,我们需要先明白模板引擎的工作原理,它的原理很简单,利用符合某种正则匹配规则(自己定义,我这里用的是{%%}和{%=%}这个,带“=”的代表是值,不带的则是一个js函数表达式。)的字符串,将之替换成js代码,并在写入的时候执行。
那怎么做呢?我们一步步来看:
首先是要匹配对应的字符串吧?用正则去匹配带{%%}和{%=%}标识符的字符串。
咱们match一下,可以看到我们所有带有所需标识符的各个片段都被我们搜索出来了。
接下来呢?
看下这段代码:
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);
再看看输出:
是不是很神奇?因为我写的是一段字符串啊。console.log()那些代码都是字符串啊,居然还能执行?这是因为js本身构造函数时就具备了这个能力,这样跟我们想要的结果好像很接近很接近了,对吧?
实际上我们要做的,就是取出逻辑部分(js代码执行部分),跟字符串部分,装进new Function,这样就基本实现了我们想要的结果。js是一门动态语言,用在这上是最方便也最容易的。(静态语言类似于C#据说要先将模板转成动态类,再用反射动态执行类中的代码,比较复杂)
现在我们完成了哪些呢?
第一:拿到要修改成js逻辑的字符串片段
第二:知道怎么把字符串转成js逻辑代码
那还差什么呢?注意到new function内部的字符串是一整段的,而我们现在有的还只是一小段一小段的片段,最重要的一步来了,拼接,拼接成一个特殊的字符串,看看这个字符串长什么样哈,你会发现,哦。。。原来是这样。。。
那每一段都有了,要拼凑成一个这样的代码不是很简单了吗?确实不难,注意下细节就OK了。
这里的话replace是替换,当正则匹配有多条规则时,其回调返回的参数这里做下小说明:
match:是匹配出来的字符片段
rank1:正则匹配当有多条规则时,有个优先级,从左到右一个个匹配(“|”根据或符号),是第一个级别的带匹配符号{%=%}的整段字符串。
content1:则是rank1对应的不带匹配符号的内部内容。后续rank2,content2也是一样
offset是偏移量,对应的是这个片段在当前整个字符串的位置。
另外的话呢,由于我们是把字符串转成js代码,会存在着XSS漏洞攻击的可能,所以把代码转成安全的符号,大概这样:
(这部分还没嵌进去)
最后呢,贴一下代码吧:(代码贴上去不会换行,勉强贴成这样子了)
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,"&")
.replace(/</g,"<")
.replace(/>/g,">")
.replace(/"/g,""")
.replace(/'/g,"'");
},
test () {
}
}
export default _Tmpl;
现在暂时没push上去。
这样一个简单的模板引擎就出来了= =。是不是很cool?