Mustache
是模板引擎思想的奠基者,Vue
中的模板引擎也借用了它的思想Vue
的模板引擎之前,先学习Mustache
能加更容易理解模板引擎的设计思想 <div id="root">div>
<script src="js/mustache.js">script>
<script>
let template = `
-
{{name}}的信息
姓名:{{name}}
年龄:{{age}}
性别:{{sex}}
`
let data = {
name: 'aa',
age: 12,
sex: '女'
}
let DOMStr = Mustache.render(template, data)
let root = document.getElementById('root')
root.innerHTML = DOMStr
script>
<div id="root">div>
<script src="js/mustache.js">script>
<script>
let template = `
{{#arr}}
-
{{.}}
{{/arr}}
`
let data = {
arr: ['A', 'B', 'C']
}
let DOMStr= Mustache.render(template, data)
let root = document.getElementById('root')
root.innerHTML = DOMStr
script>
<div id="root">div>
<script src="js/mustache.js">script>
<script>
let template = `
{{#arr}}
-
{{name}}的信息
姓名:{{name}}
年龄:{{age}}
性别:{{sex}}
{{/arr}}
`
let data = {
arr: [
{
name: 'aa',
age: 12,
sex: '女'
},
{
name: 'bb',
age: 16,
sex: '男'
},
{
name: 'cc',
age: 17,
sex: '男'
},
]
}
let DOMStr= Mustache.render(template, data)
let root = document.getElementById('root')
root.innerHTML = DOMStr
script>
<div id="root">div>
<script src="js/mustache.js">script>
<script>
let template = `
{{#arr}}
-
{{name}}的爱好是:
{{#hobbies}}
- {{.}}
{{/hobbies}}
{{/arr}}
`
let data = {
arr: [
{name: 'aa', age: 12, hobbies: ['swim', 'music']},
{name: 'bb', age: 16, hobbies: ['run', 'animation']},
{name: 'cc', age: 17, hobbies: ['joggy', 'movie']}
]
}
let templateStr = Mustache.render(template, data)
let root = document.getElementById('root')
root.innerHTML = templateStr
script>
ES6
模板语法之前,可以这样去写模版 (相比字符串拼接更优雅) <div id="root">div>
<script src="js/mustache.js">script>
<script type="text/template" id="template">
<div>
{{#show}}
<p>show</p>
{{/show}}
{{#hidden}}
<p>hidden</p>
{{/hidden}}
</div>
script>
<script>
let template = document.getElementById('template').innerText
let data = {
show: true,
hidden: false
}
let templateStr = Mustache.render(template, data)
let root = document.getElementById('root')
root.innerHTML = templateStr
script>
import parseTemplateToTokens from './parseTemplateToTokens'
import renderTemplate from './renderTemplate'
window.Mustache = {
render(templateStr, data) {
let tokens = parseTemplateToTokens(templateStr)
let DOMStr = renderTemplate(tokens, data)
console.log(DOMStr)
}
}
// 测试用例
let templateStr = `
{{#arr}}
{{name}}
{{age}}
{{sex}}
爱好:
{{#fav}}
- {{.}}
{{/fav}}
{{/arr}}
`
let data = {
arr: [
{
name: 'aa',
age: 12,
sex: '女',
fav: ['movie', 'skr']
},
{
name: 'bb',
age: 16,
sex: '男',
fav: ['study', 'icecream']
},
{
name: 'cc',
age: 17,
sex: '男',
fav: ['swim', 'sing']
}
]
}
window.Mustache.render(templateStr, data)
parseTemplateToTokens.js
import Scanner from './Scanner'
import nestTokens from './nestTokens'
export default function parseTemplateToTokens (templateStr) {
let tokens = []
let scanner = new Scanner(templateStr)
let words
// 扫描未结束
while (!scanner.eos()) {
// 扫描并返回遇到 '{{' 之前的字符
words = scanner.scanUntil('{{')
if (words) {
// 存入 tokens,并且 '{{' 之前的字符为普通文本 即 text
tokens.push(['text', words])
}
// 跳过 '{{'
scanner.scan('{{')
// 扫描并返回遇到 '}}' 之前的字符
words = scanner.scanUntil('}}')
// '{{' 和 '}}' 之间的字符为约定的语法格式,需要进行类型判断
if (words) {
if (words.startsWith('#')) { // 循环开始标记
tokens.push(['#', words.substring(1)])
} else if (words.startsWith('/')) { // 循环结束标记
tokens.push(['/', words.substring(1)])
} else { // 变量标记
tokens.push(['name', words])
}
}
// 跳过 '}}'
scanner.scan('}}')
}
// 将 tokens 形成嵌套的数组结构
tokens = nestTokens(tokens)
return tokens
}
Scanner.js
export default class Scanner {
constructor (templateStr) {
this.templateStr = templateStr
this.tail = templateStr
this.pos = 0
}
// 扫描并跳过
scan(tag) {
if (this.tail.indexOf(tag) == 0) {
this.pos += tag.length
this.tail = this.templateStr.substring(this.pos)
}
}
// 扫描并返回
scanUntil (stopTag) {
const pos_backup = this.pos
while (!this.eos() && this.tail.indexOf(stopTag) != 0) {
this.pos++
this.tail = this.templateStr.substring(this.pos)
}
return this.templateStr.substring(pos_backup, this.pos)
}
// 是否扫描结束
eos () {
return this.pos >= this.templateStr.length
}
}
nestTokens.js
// 利用 #、/ 作为入栈和出栈的标志,嵌套tokens
export default function nestTokens (tokens) {
let nestedTokens = []
let stack = []
stack.push(nestedTokens)
for (let i=0; i<tokens.length; i++) {
let token = tokens[i]
switch (token[0]) {
case '#':
token[2] = []
stack[stack.length-1].push(token)
stack.push(token[2])
break
case '/':
stack.pop()
break
default:
stack[stack.length-1].push(token)
}
}
return nestedTokens
}
renderTemplate.js
// 递归的将 tokens 转换成 DOMStr
export default function renderTemplate (tokens, data) {
let DOMStr = ''
for (let i=0; i<tokens.length; i++) {
let token = tokens[i]
switch (token[0]) {
case 'text':
DOMStr += token[1]
break
case 'name':
token[1] === '.'
? DOMStr += data
: DOMStr += data[token[1]]
break
case '#':
for (let i=0; i<data[token[1]].length; i++) {
let depData = data[token[1]][i]
DOMStr += renderTemplate(token[2], depData)
}
}
}
return DOMStr
}