什么是模板引擎?
模板引擎是将数据变为视图的最优雅方案
一:mustache的基本使用
mustache官方git: https://github.com/janl/mustache.js
mustache是“胡子“的意思,因为它的嵌入标记{{ }}非常像胡子
{{ }}的语法也被Vue沿用。
1:基本渲染数据
v-text,v-html
//渲染模板
var templateStr=`<h1>这是{{str1}}的{{str2}}</h1>`
//数据
var Obj = {
str1:'一个',
str2:'模板'
}
//把数据渲染到模板上
var htmlStr=Mustache.render(templateStr,Obj)
console.log(htmlStr);
2:数组循环渲染数据
v-for
// {{#arr}}{{/arr}}要循环的数组
var temstr =`
<ul>
{{#arr}}
<li>我叫{{name}},今年{{age}}岁</li>
{{/arr}}
</ul>
`
var obj={
arr:[{name:'小张',age:'20'},
{name:'小红',age:'21'},
{name:'小明',age:'22'}]
}
var htmlStr= Mustache.render(temstr,obj)
console.log(htmlStr);
3:嵌套数组循环渲染数据
// {{#arr}}{{/arr}}要循环的数组
var temstr =`
<ul>
{{#arr}}
<li>我叫{{name}},今年{{age}}岁,爱好
{{#hobbies}}
<span>{{.}}</span>
{{/hobbies}}
</li>
{{/arr}}
</ul>
`
var obj={
arr:[{name:'小张',age:'20',hobbies:['看书','学习']},
{name:'小红',age:'21',hobbies:['化妆','逛街']},
{name:'小明',age:'22',hobbies:['跑步','篮球']}]
}
var htmlStr= Mustache.render(temstr,obj)
console.log(htmlStr);
4:布尔值
v-if
var temstr =`
<ul>
{{#m}}
<li>A</li>
{{/m}}
{{#w}}
<li>B</li>
{{/w}}
</ul>
`
var obj = {
m:true,
w:false
}
var htmlStr= Mustache.render(temstr,obj);
5:模板引擎实例
<div id="test"></div>
<script src="./node_modules/mustache/mustache.js"></script>
<script type="text/template" id="templateStr">
<ul>
{{#arr}}
<li>
<h1>{{name}}</h1>
<div>{{age}}</div>
{{#hobby}}
<span>{{.}}</span>
{{/hobby}}
</li>
{{/arr}}
</ul>
</script>
<script>
var tStr=document.getElementById('templateStr').innerHTML;
var obj={
arr:[
{name:'小王',age:'19',hobby:['学习','睡觉']},
{name:'小红',age:'20',hobby:['逛街','睡觉']},
{name:'小张',age:'21',hobby:['跑步','篮球']},
]
}
document.getElementById('test').innerHTML = Mustache.render(tStr,obj);
</script>
二:mustache的底层核心机理
1:正则表达式思路
.replace( RegExp 对象 , fun )
RegExp 对象:正则表达式
用正则表达式找到相应的字符:列:找到 {{}} 正则表达式/\{\{(\w+)\}\}/g
var htmlstr="<h1>我买了一个{{thing}},是{{mood}}</h1>"
var obj={
thing:'手机',
mood:'华为'
}
var newhtmlstr = htmlstr.replace(/\{\{(\w+)\}\}/g,function(findStr,$1){
// 替换目标:findStr {{thing}}
//表达式内匹配的文本:$1 thing
return obj[$1]
})
封装一个简单的render
var htmlstr="<h1>我买了一个{{thing}},是{{mood}}</h1>"
var obj={
thing:'手机s',
mood:'华为'
}
function render(templateStr,data){
return templateStr.replace(/\{\{(\w+)\}\}/g,function(findStr,$1){
return data[$1]
})
}
但以上只能对简单的数据进行处理
要对复杂的数据进行处理就要用到tokens
mustache底层重点要做两件事
1);将模板字符串编译为token形式
2):将tokens结合数据,解析为dom字符串
三:手写mustache库
1:配置webpack
1)初始化包管理 npm init
2)下载webpack相关包
webpack webpack-cli webpack-dev-server
3)新建并配置 webpack.config.js 文件
const path = require('path')
module.exports = {
//模式:开发
mode:'development',
//入口
entry:'./src/index.js',
//打包到什么文件
output:{
filename:'bundle.js'
},
//配置wabpack-dev-server
devServer:{
//静态文件根目录
contentBase:path.join(__dirname,'www'),
//不压缩
compress:false,
//端口号
port:8080,
//虚拟打包路径,bundle.js文件没有真正生成
publicPath:'/xuni/'
}
}
4)创建入口文件可和静态文件
5)在package.json文件中添加运行命令
"dev": "webpack-dev-server"
2:scanner类
这个类的主要目的就是以“{{”,“}}”为截取对象,截取字符串并分段返回出来。
export default class Scanner{
constructor(templateStr){
this.templateStr=templateStr;
//指针
this.pos=0;
//尾巴
this.tail=templateStr;
}
//功能弱,就是走过指定内容,没有返回值
scan(stopTag){
if(this.tail.indexOf(stopTag)==0){
this.pos+=stopTag.length;
this.tail=this.templateStr.substr(this.pos)
}
}
//让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
scanUtile(stopTag){
const pos_backup=this.pos;
//当尾巴的开头不是stoptap的时候,说明还没有扫描到stoptag
// 指针长度小于模板长度说明改模板还没查找完
while(this.tail.indexOf(stopTag)!=0&& !this.eos()){
this.pos++;
//改变尾巴从当前指针的这个字符开始。到最后全部
this.tail=this.templateStr.substr(this.pos)
}
return this.templateStr.substring(pos_backup,this.pos)
}
// 判断指针是否到头
eos(){
return this.pos>=templateStr.length;
}
}
3:获取Token数组
生成token数组
import Scanner from "./Scanner.js"
import nestTokens from "./nestTokens.js"
export default function parseTemplateTotoken(templateStr){
var tokens=[];
var scanner = new Scanner(templateStr);
let words=null;
while(!scanner.eos()){
// 将模板除{{}}外的所有字符封开
words=scanner.scanUtile('{{');
if(words!=''){
tokens.push(['text',words])
}
scanner.scan('{{');
// 对{{}}内的字符串做数据类型判断
words=scanner.scanUtile('}}');
if(words!=''){
if(words[0]=='#'){
tokens.push(['#',words.substring(1)]);
}else if(words[0]=='/'){
tokens.push(['/',words.substring(1)]);
}else{
tokens.push(['name',words])
}
}
scanner.scan('}}');
}
return nestTokens(tokens);
}
结果
此时的token字符串数组还只是一个一维数组,并不能直接拿来渲染
还需要做二次处理,上面中循环时我们对每个字符串做了判断分类:如text,name,#,/。为为二次处理做标识符
实现数组嵌套将一维数组转成可渲染的二维数组
实现思想:栈数据结构,现进后i出
export default function nestTokens(tokens){
var nestedToken=[];
// 收集器
var collector=nestedToken;
// 栈
var sections=[];
for(let i=0;i<tokens.length;i++){
let token=tokens[i];
switch (token[0]) {
case '#':
// 收集器中放入这个token
collector.push(token);
// 入栈
sections.push(token);
// 收集器要换人。给token添加为2的下标,并让收集器指向它
collector=token[2]=[];
break;
case '/':
// 出栈
let section_pop=sections.pop();
//改变收集器为栈结构队尾(队尾是栈顶),那项的下标为2的数组
collector=sections.length>0?sections[sections.length-1][2]:nestedToken;
break;
default:
// 不管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为21的数组
collector.push(token)
break;
}
}
return nestedToken;
}
5:lookup函数
识别 . 点符号
export default function lookup(dataObj,keyName){
if(keyName.indexOf('.')!=-1){
var keys = keyName.split('.')
var temr = dataObj;
for(let i=0;i<keys.length;i++){
temr=temr[keys[i]]
}
return temr;
}
return dataObj[keyName];
}
4:将Token数组变为DOM
renderTemplate.js
import lookup from './lookup.js'
import parseArray from './parseArray.js'
export default function renderTemplate(tokens,data){
var resUltStr = '';
for(let i=0;i<tokens.length;i++){
let token=tokens[i];
if(token[0]=='text'){
resUltStr+=token[1]
}else if(token[0]=='name'){
resUltStr+=lookup(data,token[1])
}else if(token[0]=='#'){
resUltStr+=parseArray(token,data)
}
}
return resUltStr;
}
parseArray.js
import lookup from './lookup.js'
import renderTemplate from './renderTemplate.js'
export default function parseArray(token,data){
var v=lookup(data,token[1]);
var resultStr = '';
for(let i=0;i<v.length;i++){
resultStr+=renderTemplate(token[2],{
...v[i],
'.':v[i],
})
}
return resultStr;
}
5:结果
<div id="asd"></div>
<script src="/xuni/bundle.js"></script>
<script>
var templateStr=`
<div>
<ul>
{{#students}}
<li>
学生是{{name}}的爱好是
<ul>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ul>
</li>
{{/students}}
</ul>
</div>`
var data={
students:[
{name:'小米',hobbies:['游泳','健身']},
{name:'小王',hobbies:['学习','睡觉','洗澡']},
{name:'小马',hobbies:['吃饭','逛街']}
]
}
var htmlstr=SSg_TemplateEngine.render(templateStr,data);
document.getElementById('asd').innerHTML=htmlstr
import parseTemplateTotoken from './parseTemplateTotoken.js'
import renderTemplate from './renderTemplate.js'
window.SSg_TemplateEngine={
render(templateStr,data){
var token = parseTemplateTotoken(templateStr);
return renderTemplate(token,data)
}
}