Vue 源码学习之mustache模板引擎
该博文是在学习尚硅谷的vue 源码教程同时做的笔记。课程都可以在b站搜到的哦。
mustache模板引擎
什么是模板引擎
模板引擎是将数据要变为视图最优雅的解决方案。
历史上出现的数据变为视图的方法:
- 纯DOM法:非常笨拙,没有实战的价值
- 数组join法:曾几何时非常流行
- ES6的反引号法:
${a} - 模板引擎:解决数据变为视图的最优雅的方法
纯DOM法:
const arr = [
{"name":xxx,"age":12},
{"name":xxx,"age":12},
{"name":xxx,"age":12},
]
let list = docunment.getElementById('list')
for(let i = 0;i<arr.length; i++){
let oLi = document.createElement('li')
oLi.innerText = arr[i].name
list.appendChild(oLi)
}
数组join法:
const arr = [
{"name":xxx,"age":12},
{"name":xxx,"age":12},
{"name":xxx,"age":12},
]
let list = docunment.getElementById('list')
for(let i = 0;i<arr.length; i++){
list.innerHTML += [
'<li>'
' <p>'+arr[i].name+'/p>'
'</li>'
].join('')
}
ES6的反引号法:
const arr = [
{"name":xxx,"age":12},
{"name":xxx,"age":12},
{"name":xxx,"age":12},
]
let list = docunment.getElementById('list')
for(let i = 0;i<arr.length; i++){
list.innerHTML += `
<li>
<p>${arr[i].name}</p>
</li>
`
}
mustache 的基本使用
- 官方
github :https://github.com/janl/mustache.js - mutache是最早的模板引擎库,比vue诞生的早的多,他的底层实现机理在当时是非常有创造性的、轰动性的,为后续模板引擎的发展提供了崭新的思路
必须引入mustance库,可以在bootcdn.com 找到他
循环对象数组
Mustache.render(templateStr,data) 负责循环对象数组并填充好
let str = `
{{#arr}}
<li>{{name}}/li>
{{/arr}}
<p>{{time}}</p>
`
const data= {
arr = [
{"name":xxx,"age":12},
{"name":xxx,"age":12},
{"name":xxx,"age":12},
],
time:'2022'
}
Mustache.render(str,data)
也可以循环简单数组
let str = `
{{#arr}}
<li>{{.}}/li>
{{/arr}}
`
const data = {
arr=['x','y','z']
}
Mustache.render(str,data)
也可以数组嵌套循环
const data = {
arr:[
{name:'xxx',hobbies:['x','y','z']},
{name:'xxx',hobbies:['x','y','z']},
{name:'xxx',hobbies:['x','y','z']}
]
}
let str = `
{{#arr}}
<li>{{name}}/li>
{{#hobbies}}
{{.}}
{{/hobbies}}
{{/arr}}
`
Mustache.render(str,data)
data也可以传入布尔值,效果类似v-if
const data = {
boolean:false
}
let str = `
<div>
{{#bollean}}
<p>xxx</p>
{{/bollean}}
</div>
`
Mustache.render(str,data)
解决反引号繁杂的问题
<script type="text/template" id="first">
<div>
{{#bollean}}
<p>xxx</p>
{{/bollean}}
</div>
</script>
<script>
let str = document.getElementById('first')
const data = {
boolean:false
}
Mustache.render(str,data)
</script>
mustache的底层核心机理
mustache不能用简单的正则表达式思路实现
较为简单的时候可以用正则实现
let templateStr = `<h1>我买了一个{{thing}},好{{mood}}</h1>`
var data = {
thing:'华为',
mood:'开心'
}
function render(templateStr,data){
return templateStr.replace(/\{\{\(\w+)\}/g,function(findStr,$1){
return data[$1]
})
}
var result = render(templateStr,data)
当较为复杂时不能使用,较为复杂时采用以下机制 )]
- tokens是一个js的嵌套数组,说白了就是模板字符串的js表示
- 它是“抽象语法树”、“虚拟节点”等等的开山鼻祖
let str = `<h1>我买了一个{{thing}},好{{mood}}</h1>`
let token = [
["text","<h1>我买了一个"],
["name","thing"],
["text","好"],
["name","mood"]
["text","啊</h1>"]
]
当模板字符串中有循环存在时,他会被编译为嵌套更深的tokens 如
[
["text","<ul>"],
["#","arr",[
["text","<li>"]
["name","."]
["text","</li>"]
]],
["text","</ul>"]
]
mustache库底层重点要做两个事情
- 将模板字符串编译为tokens形式
- 将tokens结合数据,解析为dom字符串
手写实现mustache库
使用webpack和webpack-dev-serve构建
- 手写Scanner类
- 手写html=>tokens
- 手写将tokens=>注入数据
<script>
let tempalteStr = `<h1>我买了一个{{thing}},好{{mood}}</h1>`
var data = {
thing:'华为',
mood:'开心'
}
TemplateEngine.render()
</script>
import parseTemplateToTokens from './parseTemplateToTokens.js'
import renderTemplate from './renderTemplate.js'
window.TemplateEngine = {
render(templateStr,data){
let tokens = parseTemplateToTokens(templateStr)
let domStr = renderTemplate(tokens,data)
return domStr
}
}
新建一个Scanner类
export default class Scanner{
constructor(templateStr){
this.templateStr = templateStr
this.pos = 0
this.tail = templateStr
}
scan(tag){
if(this.tail.indexOf(tag) == 0){
this.pos += tag.length
this.templateStr.substring(this.pos)
}
}
scanUtil(stopTag){
const pos_backup = this.pos
while(this.tail.indexOf(stopTag)!=0 && this.post<this.templateStr.length>){
this.pos++
this.tail = this.templateStr.substring(this.pos)
}
return this.tempalteStr.substring(pos_backup,this.pos)
}
}
新建一个html和token转化的js
import Scanner from './Scanner.js'
import nestTokens from './nestTokens.js'
export default function parseTemplateToTokens(templateStr){
var tokens = []
let words;
let scanner = new Scanner(templateStr)
while(scanner.pos != templateStr.length){
words = scanner.scanUtil('{{')
if(words != ''){
let isInJJH = false
let _words = ''
for(let i = 0;i<words.length;i++){
if(words[i]) == '<'){
isInJJH = true
}else if(words[i] == '>'){
isInJJH = false
}
if(words[i] != ''){
_words += words[i]
}else{
if(isInJJH){
_words += words[i]
}
}
}
tokens.push(['text'],words)
}
scanner.scan('{{')
words = scanner.scanUtil('}}')
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)
}
新建一个nestTokens.js用于折叠tokens,将#和/之间的tokens能够整合起来,解决嵌套问题 十分精妙及重要
sections栈是展示层级的关系,collector是根据层级移动的指针窗口,nestedTokens是具体的层级内容展示
export default function nestTokens(tokens){
let nestedTokens = []
let sections = []
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 = secitons.length > 0?sections[sections.length-1][2]:nestedTokens
break;
default:
collector.push(token)
}
}
return nestedTokens
}
新建一个renderTemplate.js,让tokens变为DOM字符串
import lookup from './lookup.js'
import parseArray from './parseArray.js'
export default function renderTemplate(tokens,data){
let resStr = ''
for(let i = 0; i < tokens.length; i++){
let token = tokens[i]
if(token[0] == 'text'){
resStr += token[1]
}else if(token[0] == 'name'){
resStr += lookup(data,token[1])
}else if(token[0] == '#'){
resStr += parseArray(token,data)
}
}
}
当toke为name时我们要在data中寻找这个数据,当data数据结构比较复杂时,无法用object.name的方法访问数据
新建一个lookup.js解决上面的问题
export default function lookup(dataObj,keyName){
if(keyName.indexOf('.') != -1 && keyName != '.'){
let keys = keyName.split('.')
let temp = dataObj;
for(let i = 0;i<keys.length;i++){
temp = temp[keys[i]]
}
return temp
}
return dataObj[keyName]
}
这个方法还是面试算法题之一哦
新建一个parseArray.js处理数组,结合renderTemplate实现递归。注意这个函数接收的是token,不是tokens
import renderTemplate from './renderTemplate.js'
import lookup from './lookup.js'
export default function parseArray(token,data){
let v = lookup(data,token[1])
let resStr = ''
for(let i = 0;i<v.length;i++){
resStr += renderTemplate(token[2],{
'.':v[i],
...v[i]
})
}
return resStr
}
import lookup from './lookup.js'
export default function parseArray(token,data){
let v = lookup(data,token[1])
let resStr = ''
for(let i = 0;i<v.length;i++){
resStr += renderTemplate(token[2],{
'.':v[i],
...v[i]
})
}
return resStr
}
|