一、背景
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