什么是模板引擎,平时使用Vue写循环的时候用v-for那么溜却从来没有想过为什么
mustache基本使用
mustache的底层核心机理
那么什么是tokens?
tokens是一个JS的嵌套数组,说白了,就是
mustache库底层重点要做两个事情:
① 将模板字符串编译为 tokens 形式 ② 将 tokens 结合数据,解析为 dom 字符串板字符串的JS表示
带你手写实现mustache库
将模板字符串变为简单版tokensn
首先可以看到每一行的第一列都有不一样的符号"name","text","#",首先通过ScanUntil函数进行指针pos移动和对应数据字段收集,Scan根据{{,}}等标识符的长度移动pos指针,方便下一次收集
export default class Scaner{
constructor(template){
console.log(template)
this.template = template
this.pos = 0
this.tail = template
}
scan(tag){
if(this.tail.indexOf(tag) == 0){//找到了现在要取找到的字符的长度,指针pos移动该长度,方便下一次寻找
this.pos += tag.length
this.tail = this.template.substr(this.pos)
}
}
sanUtil(stopTag){
var pos_backup=this.pos
//0是找到
while(this.tail.indexOf(stopTag) != 0 && !this.eos()){
this.pos ++;
//截取字段为[this.pos,最后]
this.tail = this.template.substring(this.pos)
}
return this.template.substring(pos_backup,this.pos)
}
eos(){
return this.pos >= this.template.length
}
}
parseTemplateToToken函数进行将模板字符串进行拆解
====>
import Scanner from './Scanner'
import nestToken from './nestToken'
export default function parseTemplateToTokens(templateStr){
var tokens =[]
var scanner = new Scanner(templateStr)
var words
while (!scanner.eos()){
words = scanner.sanUtil('{{')
if (words !=""){
var classIn = false
var _words = '';
for(var i =0;i<words.length;i++){
//用正则判断是否有空格
//没有直接加
if(words[i]== '<'){
classIn = true
}else if(words[i] == '>'){
classIn = false
}
if(!/\s/.test(words[i])){
_words += words[i]
}else{
if(!classIn){
_words += ''
}
}
//有判断是否再< >里面
}
tokens.push(['text',_words])
}
scanner.scan("{{")
words = scanner.sanUtil('}}')
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("}}")
}
console.log("原数组",tokens)
return nestToken(tokens)
}
将模板字符串变为复杂版tokens
简单版只是通过{{ ,}}进行拆解但是没有实现嵌套如下图的左图,好比要实现下图右图的#hobbies是在#student数组里面,那么如何实现呢
我们的目的是为了数据结构化形成,怎末把hobbies嵌套到student里面呢??思考一下((()))这么多括号是不是互相嵌套呢,我们怎末知道是在那个括号里面?可以通过栈将(依次压入,每次压入指针都在变化,栈顶这时候指向的是第三个左括号,等到遇到右括号),也就说明子括号的结束,然后再改变指针指回上一层(上一级)的左括号,依次类推。那么我们这时候在收集数据,怎末知道那个数据放在哪里,这就很好理解拉,遇到#,就好像遇到左括号,指针指向#,就知道放在那个括号里面拉,如果再遇到新的#,指针改变状态再次指向那个新的#,就可以往里面放东西,遇到“/”就知道改变指针退回上一级的#。
在js里没有指针这个概念,但是可以想到的是引用类型的浅复制里面,复制了简单的数据结构,但是指向的还是同一个地址,所以可以浅复制出一个数组作为指针。
我们通过nestTokens函数实现
/*
函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
var nestedToken = []
var sections = []
var colltector = nestedToken
for (var i = 0 ;i <tokens.length;i++){
let token = tokens[i]
switch(token[0]){
case '#':
colltector.push(token)
sections.push(token)
//开始收集
colltector = token[2] = []
break
case '/':
sections.pop()
colltector = sections.length > 0 ? sections[sections.length - 1][2] : nestedToken;
break;
default:
colltector.push(token)
}
}
console.log("嵌套成",nestedToken)
return nestedToken
};
将tokens结合数据,解析为dom字符串
首先思考一个问题你拿到tokens和数据,你要渲染几次,当然是根据数据决定拉,因为是把数据往模板上套。
这里要注意的是模板里面的key要一样,比如students,hobbies,name,
一开始如果是简单没有数组嵌套的话我们直接通过text对应的文本直接用字符串进行拼接,name也是赋值拼接
这里我们用RenderTemplate函数
import lookup from './lookup.js';
import parseArray from './parseArray';
/*
函数的功能是让tokens数组变为dom字符串
*/
export default function renderTemplate(tokens, data) {
// 结果字符串
console.log("结果字符串",tokens)
var resultStr = '';
// 遍历tokens
for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];
// 看类型
if (token[0] == 'text') {
// 拼起来
resultStr += token[1];
} else if (token[0] == 'name') {
// 如果是name类型,那么就直接使用它的值,当然要用lookup
// 因为防止这里是“a.b.c”有逗号的形式
resultStr += lookup(data, token[1]);
} else if (token[0] == '#') {
console.log("以#开头",token)
resultStr += parseArray(token, data);
}
}
console.log(resultStr)
return resultStr;
}
如果是数组嵌套的话我们用parseArray函数, name对应的key值就有a.b.c,比如student里面还有个出生日期,那么我们再vue中肯定是写birth.year,birth.month,birth.day,但是数据只会给student:[ name:'xixi',birth:{year:1999,month:9,day:9}}
注意了数据没有点,所以为了方便结合,我们在嵌套数组的前面加个键值对
然后我们在lookup函数用高阶函数对a.b.c进行处理,是学习那个数据响应式原理源码的
export default function lookup(dataObj,keyName){
if (keyName.indexOf('.') != -1 && keyName != '.') {
var getter = aa(keyName)
return getter(dataObj)
}
return dataObj[keyName]
}
function aa(keyName){
var segments =keyName.split('.')
return dataObj=>{
for(let i = 0,l=segments.length;i<l;i++){
dataObj = dataObj[segments[i]]
console.log(dataObj)
}
return dataObj
}
}
最后在页面渲染上树页面
// 渲染上树
var container = document.getElementById('container');
container.innerHTML = domStr;