一、背景
doT.js 灵感来源于搜寻基于 V8 和 Node.js ,强调性能,最快速最简洁的 JavaScript 模板函数。 它在 Node.js 和浏览器两端都彰显出卓越的性能。 在我搜寻期间,两款模板引擎引起了我的注意,激发了创造 doT 的灵感。
第一个是 jQote2,一个 jQuery 插件,它使用字符串拼接,避免使用 'with' 声明,它是首个追求速度的引擎。
第二个是 underscore.js ,其中有一个设计精巧、扩展友好的模板函数。doT.js 快速,小巧且毫无依赖。
前端渲染有很多框架,而且形式和内容在不断发生变化。这些演变的背后是设计模式的变化,而归根到底是功能划分逻辑的演变:MVC—>MVP—>MVVM(忽略最早混在一起的写法,那不称为模式)。近几年兴起的React、Vue、Angular等框架都属于MVVM模式,能帮我们实现界面渲染、事件绑定、路由分发等复杂功能。但在一些只需完成数据和模板简单渲染的场合,它们就显得笨重而且学习成本较高了。
例如,在美团外卖的开发实践中,前端经常从后端接口取得长串的数据,这些数据拥有相同的样式模板,前端需要将这些数据在同一个样式模板上做重复渲染操作。
解决这个问题的模板引擎有很多,doT.js(出自女程序员Laura Doktorova之手)是其中非常优秀的一个。下表将doT.js与其他同类引擎做了对比:
从上可以看出doT.js更值得推荐,它的主要优势在于:
- 小巧精简,源代码不超过两百行,6KB的大小,压缩版只有4KB;
- 支持表达式丰富,涵盖几乎所有应用场景的表达式语句;
- 性能优秀;
- 不依赖第三方库。
二、DOT.js的API标签介绍
1、{{ }} JS原生态代码
2、{{= }} 变量运算,赋值 {{=it.f1 + it.f2}}
3、{{! }} 赋值并且编码
4、{{# }}
5、{{## #}}
6、{{? }} 条件语句
7、{{~ }} 循环
注:其实条件语句和循环可以用{{if}}{{else if}}{{for(var i=0;i<length;i++)}}来代替,也就是JS的原生态代码
<script type="text/html" id="tpl">
<div>
<a>name:{{= it.name}}</a>
<p>age:{{= it.age}}</p>
<p>hello:{{= it.sayHello() }}</p>
<select>
{{~ it.arr:item}}
<option {{?item.id == it.stringParams2}}selected{{?}} value="{{=item.id}}">
{{=item.text}}
</option>
{{~}}
</select>
</div>
</script>
<script>
$("#app").html(doT.template($("#tpl").html())({
name:'stringParams1',
stringParams1:'stringParams1_value',
stringParams2:1,
arr:[{id:0,text:'val1'},{id:1,text:'val2'}],
sayHello:function () {
return this[this.name]
}
}));
</script>
以上代码可以看出doT.js的设计思路:将数据注入到预置的视图模板中渲染,返回HTML代码段,从而得到最终视图。
和后端渲染不同,doT.js的渲染完全交由前端来进行,这样做主要有以下好处:
- 脱离后端渲染语言,不需要依赖后端项目的启动,从而降低了开发耦合度、提升开发效率;
- View层渲染逻辑全在JavaScript层实现,容易维护和修改;
- 数据通过接口得到,无需考虑后端数据模型变化,只需关心数据格式。
三、DOT.js使用方法
1、最常规用法{{=it.attr}}
<!-- 要显示的区域 -->
<div id="testid"></div>
<script type="text/x-dot-template" id="useType0">
<p>
<span>姓名:{{=it.name}}</span>
<span>年龄:{{=it.age}}</span>
<span>爱好:{{=it.fun}}</span>
</p>
</script>
<!-- 引入js文件 -->
<script src="https://cdn.bootcss.com/dot/1.1.0/doT.js"></script>
<script>
var testjson={"name":"moyun","age":30,"fun":"逛吃逛吃逛吃逛吃逛吃"},//定义要渲染数据,一般是从后台ajax拉取
tmpltxt=doT.template(document.getElementById("useType0").innerHTML);//生成模板方法
document.getElementById("testid").innerHTML=tmpltxt(testjson);//数据渲染
</script>
运行的结果如下图:
2、循环数组{{~}}
<div id="testid"></div>
<!-- 模板存放区域 修改type类型,以免会被解析成js -->
<script type="text/x-dot-template" id="useType0">
<ul>
{{~it:value:index}}
<li><span>姓名:{{=value.name}}</span><span>年龄:{{=value.age}}</span><span>爱好:{{=value.fun}}</span></li>
{{~}}
</ul>
</script>
<script src="https://cdn.bootcss.com/dot/1.1.0/doT.js"></script>
<script>
var testjson=[
{"name":"张三","age":31,"fun":"吃东西"},
{"name":"李四","age":24,"fun":"上网"},
{"name":"王五","age":70,"fun":"散步,跑步"}
],
tmpltxt=doT.template(document.getElementById("useType0").innerHTML);//生成模板方法
document.getElementById("testid").innerHTML=tmpltxt(testjson);//数据渲染
</script>
运行结果如下:
3、条件渲染{{?}}{{??}},相当于原生的if else if
运行模式:
格式:
{{? }} if
{{?? }} else if
{{??}} else
数据源:{“name”:”Jake”,”age”:31}
区域:<div id=”condition”></div>
模板:
<script id=”conditionstmpl” type=”text/x-dot-template”>
{{? !it.name }}
<div>Oh, I love your name, {{=it.name}}!</div>
{{?? !it.age === 0}}
<div>Guess nobody named you yet!</div>
{{??}}
You are {{=it.age}} and still dont have a name?
{{?}}
</script>
调用方式:
var dataEncode = {“uri”:”http://grycheng.com/?keywords=Yoga”,”html”:”<div style=’background: #f00; height: 30px; line-height: 30px;’>html元素</div>”};
var EncodeText = doT.template($(“#encodetmpl”).text());
$(“#encode”).html(EncodeText(dataEncode));
<div id="testid"></div>
<!-- 条件渲染{{?}}{{??}},相当于原生的if else if -->
<script type="text/x-dot-template" id="useType0">
<ul>
{{~it:value:index}}
{{?!value.age}}
<li><span>姓名:{{=value.name}}</span><span>年龄:年龄数据缺失</span><span>爱好:{{=value.fun}}</span></li>
{{??!value.fun}}
<li><span>姓名:{{=value.name}}</span><span>年龄:{{=value.age}}</span><span>爱好:无趣的人</span></li>
{{??}}
<li><span>姓名:{{=value.name}}</span><span>年龄:{{=value.age}}</span><span>爱好:{{=value.fun}}</span></li>
{{?}}
{{~}}
</ul>
</script>
<script src="https://cdn.bootcss.com/dot/1.1.0/doT.js"></script>
<script>
var testjson=[
{"name":"张三","age":31,"fun":"吃东西"},
{"name":"李四","fun":"上网"},
{"name":"王五","age":70}
],
tmpltxt=doT.template(document.getElementById("useType0").innerHTML);
document.getElementById("testid").innerHTML=tmpltxt(testjson);
</script>
运行结果如下:
4、编码渲染{{!}},主要是为了防止代码注入以保障安全,如传入一个HTML片段或js片段,它会以字符串的形式渲染
<div id="testid"></div>
<script type="text/x-dot-template" id="useType0">
<ul>
{{~it:value:index}}
{{?value.bz}}
<li><span>姓名:{{=value.name}}</span><span>年龄:{{=value.age}}</span><span>爱好:{{!value.html}}</span></li>
{{??}}
<li><span>姓名:{{=value.name}}</span><span>年龄:{{=value.age}}</span><span>爱好:{{=value.html}}</span></li>
{{?}}
{{~}}
</ul>
</script>
<script src="https://cdn.bootcss.com/dot/1.1.0/doT.js"></script>
<script>
var testjson=[
{"name":"张三","age":31,"html":"<b>这里是一个html标签</b>","bz":true},
{"name":"李四","age":24,"html":"<b>另外一个标签</b>","bz":false}
],
tmpltxt=doT.template(document.getElementById("useType0").innerHTML);
document.getElementById("testid").innerHTML=tmpltxt(testjson);
</script>
运行结果如下:
5、支持共用模块定义{{##def.}}定义,{{#def.}}引用模块
<div id="testid"></div>
<script type="text/x-dot-template" id="useType0">
<!-- 模块定义0 -->
{{##def.togeter0:
<li>
<span>姓名:{{=value.name}}</span>
<span>年龄:{{=value.age}}</span>
<span>爱好:{{!value.html}}</span>
</li>
#}}
<!-- 模块定义1 -->
{{##def.togeter1:
<li>
<span>姓名:{{=value.name}}</span>
<span>年龄:{{=value.age}}</span>
<span>爱好:{{=value.html}}</span>
</li>
#}}
<ul>
{{~it:value:index}}
{{?value.bz}}
<!-- 引用模块0 -->
{{#def.togeter0}}
{{??}}
<!-- 引用模块1 -->
{{#def.togeter1}}
{{?}}
{{~}}
</ul>
</script>
<script src="https://cdn.bootcss.com/dot/1.1.0/doT.js"></script>
<script>
var testjson=[
{"name":"张三","age":31,"html":"<b>这里是一个html标签</b>","bz":true},
{"name":"李四","age":24,"html":"<b>另外一个标签</b>","bz":false}
],
tmpltxt=doT.template(document.getElementById("useType0").innerHTML);
document.getElementById("testid").innerHTML=tmpltxt(testjson);
</script>
运行结果如下:
注:模块定义也可以一个json数据定义,再在生成模板函数函数的时候传入即可,示例如下,其中tmpljson就是在外面以json定义的模块:
<div id="testid"></div>
<script type="text/x-dot-template" id="useType0">
{{##def.togeter0:
<li>
<span>姓名:{{=value.name}}</span>
<span>年龄:{{=value.age}}</span>
<span>爱好:{{!value.html}}</span>
</li>
#}}
{{##def.togeter1:
<li>
<span>姓名:{{=value.name}}</span>
<span>年龄:{{=value.age}}</span>
<span>爱好:{{=value.html}}</span>
</li>
#}}
<ul>
{{~it:value:index}}
{{?value.bz===true}}
{{#def.togeter0}}
{{??value.bz===false}}
{{#def.togeter1}}
{{??}}
{{#def.testmode}}
{{?}}
{{~}}
</ul>
</script>
<script src="https://cdn.bootcss.com/dot/1.1.0/doT.js"></script>
<script>
var testjson=[
{"name":"张三","age":31,"html":"<b>呵呵</b>","bz":true},
{"name":"李四","age":24,"html":"<b>哈哈</b>","bz":false},
{"name":"李四","age":24,"html":"<b>哈哈</b>"}
],
tmpljson={"testmode":"<li><span>姓名:{{=value.name}}</span><span>年龄:{{=value.age}}</span></li>"},
tmpltxt=doT.template(document.getElementById("useType0").innerHTML,undefined,tmpljson);
document.getElementById("testid").innerHTML=tmpltxt(testjson);
</script>
6、用原生的循环,条件渲染,灵活调用
<div id="testid"></div>
<script type="text/x-dot-template" id="useType0">
{{##def.togeter0:
<li><span>姓名:{{=itz.name}}</span>
<span>年龄:{{=itz.age}}</span>
<span>爱好:{{!itz.html}}</span>
</li>
#}}
{{##def.togeter1:
<li>
<span>姓名:{{=itz.name}}</span>
<span>年龄:{{=itz.age}}</span>
<span>爱好:{{=itz.html}}</span>
</li>
#}}
<ul>
{{ for(var i=0;i<it.length;i++){ }}
{{ var itz=it[i]; }}
{{ if(itz.bz){ }}
{{#def.togeter0}}
{{ }else{ }}
{{#def.togeter1}}
{{ } }}
{{ } }}
</ul>
</script>
<script type="text/javascript" src="doT.min.js"></script>
<script>
var testjson=[
{"name":"张三","age":31,"html":"<b>这里是一个html标签</b>","bz":true},
{"name":"李四","age":24,"html":"<b>另外一个标签</b>","bz":false}
],
tmpltxt=doT.template(document.getElementById("useType0").innerHTML);
document.getElementById("testid").innerHTML=tmpltxt(testjson);
</script>
四、参考
http://jinlong.github.io/doT/
https://github.com/olado/doT
https://qiaolevip.github.io/frontend-template-engines/doT.html