前言
Mustache
是模板引擎思想的奠基者,Vue
中的模板引擎也借用了它的思想- 因此,在学习
Vue
的模板引擎之前,先学习Mustache
能加更容易理解模板引擎的设计思想
Mustache
<div id="root"></div>
<script src="js/mustache.js"></script>
<script>
let template = `
<ul>
<li>
<p>{{name}}的信息</p>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>性别:{{sex}}</p>
</li>
</ul>
`
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 = `
<ul>
{{#arr}}
<li>
{{.}}
</li>
{{/arr}}
</ul>
`
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 = `
<ul>
{{#arr}}
<li>
<p>{{name}}的信息</p>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>性别:{{sex}}</p>
</li>
{{/arr}}
</ul>
`
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 = `
<ul>
{{#arr}}
<li>
{{name}}的爱好是:
<ol>
{{#hobbies}}
<li>{{.}}</li>
{{/hobbies}}
</ol>
</li>
{{/arr}}
</ul>
`
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>
Mustache 原理
Mustache
会将模版字符串先编译成 tokens
,然后再结合数据解析成 DOM 字符串
tokens
是一个嵌套的 js
数组,也是抽象语法树 AST
- 当模版存在循环时:
- 模板存在双重循环时:
手写简单源码
- 目录结构
index.js
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 = `
<div>
{{#arr}}
<p>{{name}}</p>
<p>{{age}}</p>
<p>{{sex}}</p>
<p>爱好:</p>
<ul>
{{#fav}}
<li>{{.}}</li>
{{/fav}}
</ul>
{{/arr}}
</div>
`
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)
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.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 = nestTokens(tokens)
return tokens
}
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
}
}
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
}
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
}
- 结果: