theme: condensed-night-purple
Mustache学习笔记
什么是模版引擎?
将数据要变为视图的最优雅的解决方案。
模版引擎实现原理
1.将模版解析为tokens数组
2.将tokens数组替换数据后还原回模版字符串
代码 模版和数据
// 模版
let templateStr = `
<ul>
{{#students}}
<li class="123">
{{name}}爱好是:
{{age}}
<ol>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ol>
</li>
{{/students}}
</ul>`
// 数据
const data = {
students: [{
name: 'Yang',
age: 23,
hobbies: ['写代码', '吃肉', '减肥']
}, {
name: 'ZGR',
age: 18,
hobbies: ['吃肉', '吃肉', '吃肉']
}, {
name: 'test',
age: 999,
hobbies: ['唱', '跳', 'rap', '篮球']
}]
}
/**
* 通过Mustache.render方法完成2步
* 1.把模版解析为tokens数组
* 2.把tokens数组拼接数据后合并为模版字符串
*/
const domStr = Mustache.render(templateStr, data)
// 把拼接好的模版字符串写进body标签
document.body.innerHTML = domStr
分析=>将模版解析为tokens数组
// 全局提供Mustache
window.Mustache = {
render(templateStr, data) {
// 调用parseTemplateToTokens让模版字符串变成数组
const tokens = parseTemplateToTokens(templateStr)
// 调用renderTemplate函数把tokens变为Dom字符串
return renderTemplate(tokens, data)
}
}
1.首先要实现一个扫描器 可以扫描模版字符串
export default class Scanner {
constructor(templateStr) {
// 将模版字符串保存在自身
this.templateStr = templateStr
// 指针
this.pointer = 0
// 尾巴 一开始就是模版字符串的内容
this.tail = templateStr
}
// 路过指定内容
scan(tag) {
if (this.tail.indexOf(tag) === 0) {
// tag有多长 往后移动多少位 跳过标记
this.pointer += tag.length
// 改变尾巴
this.tail = this.templateStr.substring(this.pointer)
}
}
// 让指针进行扫描 直到遇见指定内容结束并且返回结束之前扫描的文字
scanUntil(stopTag) {
// 记录执行本方法时 pointer 的值
const pointerBackUp = this.pointer
// 当尾巴的开头不是stopTag的时候,就说明还没有扫描到
while (!this.eos() && this.tail.indexOf(stopTag) !== 0) {
this.pointer++
this.tail = this.templateStr.substring(this.pointer)
}
return this.templateStr.substring(pointerBackUp, this.pointer)
}
// 指针是否到头了
eos() {
// 当前的指针大于等于templateStr的长度 说明已经到头了
return this.pointer >= this.templateStr.length
}
}
2.通过扫描器实现分割模版字符串为tokens
export default function parseTemplateToTokens(templateStr) {
// tokens数组
const tokens = []
// 创建扫描器
const scanner = new Scanner(templateStr)
// 标记前的内容
let words
// 启动扫描器
while (!scanner.eos()) {
// 收集开始标记
words = scanner.scanUntil('{{')
// 不为空存储
words && tokens.push(['text', words])
// 跳过本次开始标记
scanner.scan('{{')
// 收集结束标记
words = scanner.scanUntil('}}')
// 不为空存储
if (words !== '') {
// 如果以#开头
if (words[0] === '#') {
// 存储类型为# 值为去掉#的字符串
tokens.push(['#', words.substring(1)])
} else if (words[0] === '/') {
// 存储类型为/ 值为去掉/的字符串
tokens.push(['/', words.substring(1)])
} else {
// 如果均不是 保存类型为 name
tokens.push(['name', words])
}
}
// 跳过本次结束标记
scanner.scan('}}')
}
// 返回处理好的tokens数组 此时tokens是零散的数组
return nestTokens(tokens)
}
3.把零散的tokens解析为多维数组
export default function nestTokens(tokens) {
// 结果数组
const nestedTokens = []
// 收集器 最初指向 结果数组
let collector = nestedTokens
// 存放小tokens
const sections = []
// 遍历零散数组
tokens.forEach(token => {
// 判断token第0位
switch (token[0]) {
case '#':
// 收集器中放入这个token
collector.push(token)
// 入栈
sections.push(token)
// 换收集器 给token第二项添加一个空数组 并且让收集器指向它
collector = token[2] = []
break
case '/':
// 出栈
sections.pop()
// 改变收集器为栈结构队尾数组下标为2的数组
collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens
break
default :
collector.push(token)
break
}
})
// 返回结果 此时数据已经处理完毕
return nestedTokens
}
此时已经实现了第一步把模版字符串变成tokens数组
分析=>将tokens数组解析回模版字符串
1.实现renderTemplate此方法就是为了把tokens数组解析回模版字符串
export default function renderTemplate(tokens, data) {
// 结果字符串
let resultStr = ''
// 遍历tokens
tokens.forEach(token => {
// 如果当前的类型是text
if (token[0] === 'text') {
// 直接+=第二位就好了
resultStr += token[1]
// 如果当前的类型是name
} else if (token[0] === 'name') {
// 那么用lookUp拿出值
resultStr += lookUp(data, token[1])
} else if (token[0] === '#') {
resultStr += parseArray(token, data)
}
})
return resultStr
}
2.实现lookUp方法 此方法为了实现利用a.b.c的字符串从数据中取值
export default function lookUp(dataObj, keyName) {
// 如果keyName有. 并且不能只有.
if (keyName && keyName !== '.' && keyName.indexOf('.') !== -1) {
// 分割字符串
const keys = keyName.split('.')
// 临时变量保存数据
let temp = dataObj
keys.forEach(key => {
// 一层一层找出数据
temp = temp[key] ? temp[key] : ''
})
// 返回数据
return temp
}
// 如果没有.符号
return dataObj[keyName] || ''
}
3.实现 parseArray 实现递归
export default function parseArray(token, data) {
let v = lookUp(data, token[1])
let resultStr = ''
v.forEach(item => {
// 要判断.
resultStr += renderTemplate(token[2], {...item, '.': item})
})
return resultStr
}