Vue
源码学习之mustache模板引擎该博文是在学习尚硅谷的vue
源码教程同时做的笔记。课程都可以在b站搜到的哦。
模板引擎是将数据要变为视图最优雅的解决方案。
历史上出现的数据变为视图的方法:
${a}
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)
}
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 += [
''
' '
+arr[i].name+'/p>'
''
].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 += `
${arr[i].name}
`
}
mustache
的基本使用github
:https://github.com/janl/mustache.js
必须引入mustance库,可以在bootcdn.com
找到他
Mustache.render(templateStr,data)
负责循环对象数组并填充好
let str = `
{{#arr}}
{{name}}/li>
{{/arr}}
{{time}}
`
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>
{{/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}}
{{name}}/li>
{{#hobbies}}
{{.}}
{{/hobbies}}
{{/arr}}
`
Mustache.render(str,data)
data也可以传入布尔值,效果类似v-if
const data = {
boolean:false
}
let str = `
{{#bollean}}
xxx
{{/bollean}}
`
Mustache.render(str,data)
解决反引号繁杂的问题
mustache不能用简单的正则表达式思路实现
较为简单的时候可以用正则实现
let templateStr = `我买了一个{{thing}},好{{mood}}
`
var data = {
thing:'华为',
mood:'开心'
}
function render(templateStr,data){
//利用正则找到{{}}内的值 和 replace函数进行替换
return templateStr.replace(/\{\{\(\w+)\}/g,function(findStr,$1){
return data[$1]
})
}
var result = render(templateStr,data)
//模板字符串
let str = `我买了一个{{thing}},好{{mood}}
`
//经过编译
//tokens
let token = [
["text","我买了一个"
],
["name","thing"],
["text","好"],
["name","mood"]
["text","啊"]
]
当模板字符串中有循环存在时,他会被编译为嵌套更深的tokens
如
[
["text",""
],
["#","arr",[
["text","" ]
["name","."]
["text",""]
]],
["text",""]
]
mustache库底层重点要做两个事情
使用webpack和webpack-dev-serve构建
<script>
let tempalteStr = `我买了一个{{thing}},好{{mood}}
`
var data = {
thing:'华为',
mood:'开心'
}
TemplateEngine.render()
script>
import parseTemplateToTokens from './parseTemplateToTokens.js'
import renderTemplate from './renderTemplate.js'
window.TemplateEngine = {
render(templateStr,data){
//调用parseTemplateToTokens函数,让模板字符串能够变为tokens数组
let tokens = parseTemplateToTokens(templateStr)
//调用renderTemplate函数,让token变为DOM字符串
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){
//tag有多长,比如{{是2,就让他指针后移多少位
this.pos += tag.length
//也要改变尾巴,位当前移动后指针的后面的字符串
this.templateStr.substring(this.pos)
}
}
//让指针进行扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
scanUtil(stopTag){
//记录一下执行本方法时候pos的值
const pos_backup = this.pos
//当尾巴不是stopTag的时候,就说明还没有扫描到stopTag
//&& 为了防止因为找不到且到头了而导致死循环
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){
//寻找{{并收集指针走过的text
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('}}')
}
//返回折叠收集的tokens
return nestTokens(tokens)
}
新建一个nestTokens.js用于折叠tokens,将#和/之间的tokens能够整合起来,解决嵌套问题
十分精妙及重要
sections栈是展示层级的关系,collector是根据层级移动的指针窗口,nestedTokens是具体的层级内容展示
export default function nestTokens(tokens){
let nestedTokens = []
//栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组当前操作的这个tokens小数组
let sections = []
//定义一个收集器,天生指向nestedTokens
//收集器的指向会不断的变化
let collector = nestedTokens
//遇见#入栈,遇见/出栈
for(let i = 0;i<tokens.length;i++){
//遍历传入的tokens并获取每一个token
let token = tokens[i]
//对每一个token的类型进行判断
switch(token[0]){
case '#':
//收集器放入这个token
collector.push(token)
//入栈
sections.push(token)
//收集器要换人了
//给这个token下标为2的项创建一个数组用于收集子元素,并由收集器指向
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 = ''
//遍历tokens
for(let i = 0; i < tokens.length; i++){
let token = tokens[i]
//判断类型
if(token[0] == 'text'){
//text类型直接拼起来
resStr += token[1]
}else if(token[0] == 'name'){
//name要调用lookup函数找到对象中的数据
//lookup可以防止a.b.c的情况
resStr += lookup(data,token[1])
}else if(token[0] == '#'){
resStr += parseArray(token,data)
}
}
}
当toke为name时我们要在data中寻找这个数据,当data数据结构比较复杂时,无法用object.name的方法访问数据
新建一个lookup.js解决上面的问题
//可以在dataObj对象中,寻找用连续点符号的keyName属性
export default function lookup(dataObj,keyName){
//看看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
//token ['#',`xxx`,[]]
//递归调用的次数由数组‘xxx’的长度决定
import renderTemplate from './renderTemplate.js'
import lookup from './lookup.js'
export default function parseArray(token,data){
//先得到整体data中要使用的数组
let v = lookup(data,token[1])
//结果字符串
let resStr = ''
//遍历v数组,v一定是数组
//下面的循环十分重要
for(let i = 0;i<v.length;i++){
/****这里也很关键*****/
resStr += renderTemplate(token[2],{
//现在在v[i]的基础上补充一个.属性,完美的解决了{{.}}的情况
'.':v[i],
...v[i]
})
}
return resStr
}
import lookup from './lookup.js'
export default function parseArray(token,data){
//先得到整体data中要使用的数组
let v = lookup(data,token[1])
//结果字符串
let resStr = ''
//遍历v数组,v一定是数组
//下面的循环十分重要
for(let i = 0;i<v.length;i++){
/****这里也很关键*****/
resStr += renderTemplate(token[2],{
//现在在v[i]的基础上补充一个.属性,完美的解决了{{.}}的情况
'.':v[i],
...v[i]
})
}
return resStr
}