1. mustache模板引擎
因为该库的模板语法采用的 {{}} 酷似胡子,所以起名mustache (从mustache的git仓库搬来的一张图)
1-1 what ?
模板引擎是什么?
模板引擎是将数据转换为视图的一种解决方案
至于转换成什么样的视图,完全由程序员自己写出来的模板决定,如下一套数据,就可以转换为很多不同的视图
1-2 why ?
为什么要学习模板引擎?
mustache是最早的模板引擎库,它的底层实现机理在当时是非常有创造性的、轰动性的,为后续模板引擎的发展提供了崭新的思路。要想学会其他更丰富类库的模板引擎,学习好mustache的模板引擎是很有必要的。
1-3 how ?
怎样学习mustache模板引擎的底层原理呢?
1-3-1 学会使用mustache模板引擎库
该库的使用方法和vue很相似,简单的给大家总结一下该库的使用:
templateStr中的模板字符串,要使用mustache的模板语法
- 简单数据的嵌入
//简单插入直接使用{{变量名}}
var templateStr = `<h1>我买了一个{{thing}},好{{mood}}啊</h1>`;
var data = {
thing: '华为手机',
mood: '开心'
};
var domStr = mustache.render(templateStr, data);
- 循环数据
// {{#arr}}...{{/arr}} 表示循环该部分内容,只有一个点代表数组中是基本数据
var templateStr = `
<ul>
{{#arr}}
<li>{{.}}</li>
{{/arr}}
</ul>
`;
var data = {
arr: ['A', 'B', 'C']
};
var domStr = Mustache.render(templateStr, data);
- 复杂循环嵌套数据的嵌入
// 不需要使用students.name
let templateStr = `
<div>
{{#students}}
<div class="myli">
学生{{name}}的爱好是
<ul>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ul>
</div>
{{/students}}
</div>
`
let data = {
students:[
{name:'Tom',age:15,hobbies:['吃饭',"睡觉"]},
{name:'Alice',age:15,hobbies:['吃饭',"睡觉","学习"]}
]
}
var domStr = mustache.render(templateStr, data);
- 布尔值的使用:
//该使用类似于vue中的v-if:当m的值为false是该结构不显示,也不存在于dom树上,为ture是显示该结构
var templateStr = `
{{#m}}
<h1>你好</h1>
{{/m}}
`;
var data = {
m: false
};
var domStr = Mustache.render(templateStr, data);
- 对象
var templateStr = `<h1>我买了一个{{a.thing}}</h1>`;
var data = {
a:{
thing:"华为手机"
}
};
var domStr = mustache.render(templateStr, data);
1-3-2 掌握模板引擎的核心机理
第一步:将模板字符串解析为tokens数组
- tokens数组是什么?
js的一个嵌套数组,用来表示模板字符串。
”{{“ 和 ”}}“ 不会体现在tokens中,模板语法插入的数据为name,其余普通字符为text,循环为”#“,空格换行也会包含在其中
1.模板字符串:
<h1>我买了一个{{thing}},好{{mood}}啊</h1>
1.tokens:
[
["text", "<h1>我买了一个"],
["name", "thing"],
["text", "好"],
["name", "mood"],
["text", "啊</h1>"],
]
2.模板字符串
<div>
<ul>
{{#arr}}
<li>{{.}}</li>
{{/arr}}
</ul>
</div>
2.tokens
[
["text", "<div><ul>"],
["#", "arr", [
["text", "<li>"],
["name", "."],
["text", "</li>"]
]],
["text", "</ul></div>"]
]
该部分会有两个核心部分:①得到平铺tokens ②得到nesttokens(最终的token形状)
1. 根据templateStr模板字符串,得出平铺的tokens数组
平铺的tokens数组,没有嵌套,还不能完全代表templateStr字符串
function parseTemplateToTokens(templateStr){
const scanner = new Scanner(templateStr)
let tokens = []
while(scanner.tail != ""){
let word = scanner.scanUtil('{{')
tokens.push(['text',word])
scanner.scan("{{")
word = scanner.scanUtil('}}')
// {{内容}} 这里面的内容可能不是name
if(word[0] === "#"){
tokens.push(['#',word.substring(1)])
}else if(word[0] === '/'){
tokens.push(['/',word.substring(1)])
}else{
if(word != "")
tokens.push(["name",word])
}
scanner.scan('}}')
}
return tokens
}
//用于遍历templateStr的类
class Scanner{
constructor(templateStr){
this.templateStr = templateStr
// 遍历需要的指针
this.pos = 0
// 未遍历的字符串
this.tail = templateStr
}
// 跳过指定标记
scan(stopTag){
this.pos += stopTag.length
this.tail = this.templateStr.substring(this.pos)
}
// 遍历模板字符串,收集指定字符,并返回
scanUtil(stopTag){
let startPos = this.pos
// 不断修改tail,使 {{ 变为最开始的字符
while(this.pos < this.templateStr.length && this.tail.indexOf(stopTag) != 0){
this.pos++
this.tail = this.templateStr.substring(this.pos)
}
return this.templateStr.substring(startPos,this.pos)
}
}
2. 根据平铺的tokens数组,完善为嵌套的tokens数组
这里用到了栈:这里用到的栈本质是一个数组,具有先进后出的特点,
function(tokens){
// 栈
let sections = []
// 结果token
let nestedTokens = []
// 当前插入数组:当前正在栈顶的token的插入项token[2]或者nestedTokens
let collector = nestedTokens
for(let i=0;i<tokens.length;i++){
let token = tokens[i]
switch(token[0]){
case '#':
collector.push(token)
sections.push(token)
collector = token[2] = []
break
case '/':
sections.pop()
collector = sections.length > 0 ? sections[sections.length-1][2] : nestedTokens
break
default:
collector.push(token)
}
}
return nestedTokens
}
第二步:将tokens数组与data数据整合,得出dom字符串
function renderTemplate(tokens,data){
let domStr = ''
for(let i=0;i<tokens.length;i++){
let token = tokens[i]
if(token[0] === 'text'){
domStr += token[1]
}else if(token[0] === 'name'){
if(token[1].includes(".")){
domStr += lookup(data,token[1])
}else{
domStr += data[token[1]]
}
}else if(token[0] === "#"){
domStr += parseArray(token,data)
}
}
return domStr
}
//根据str,寻找并返回obj中对应的数据
function lookup(obj,str){
// ".".split(".") = [[],[]]
if(str !== "."){
let strArr = str.split(".")
for(let i=0;i<strArr.length;i++){
obj = obj[strArr[i]]
}
return obj
}
return obj[str]
}
//递归解析数组
function parseArray(tokens,data){
let targetArr = lookup(data,tokens[1])
let resStr = ''
for(let i=0;i<targetArr.length;i++){
//{...targetArr[i],".":targetArr[i]} 是为了处理{{.}}这种情况
resStr += renderTemplate(tokens[2],{...targetArr[i],".":targetArr[i]})
}
return resStr
}
大功告成!!