mustache库的机理
- 将模板字符串编译为tokens形式
- 将tokens结合数据,解析为dom字符串
一、tokens
tokens是js的嵌套数组:
当模板字符串中有循环存在时,它将被编译成嵌套更深的tokens。
二、模板字符串解析tokens的过程
-
定义一个Scanner类,类中包含两个方法:scan和scanUntil
scan:路过指定内容,没有返回值
scanUntil:让指针进行扫描,知道遇见指定内容结束,并返回结束之前路过的文字
-
class Scanner{
constructor(templateStr){
this.templateStr = templateStr
this.pos = 0 // 指针
this.tail = templateStr // 指针尾,刚开始时指向原字符串
}
scan(tag){
if(this.tail.indexOf(tag)===0){
// tag有多少位就让tail后移多少位
this.pos += tag.length
this.tail = this.templateStr.substring(this.pos)
}
}
scanUntil(stopTag){
// 记录执行本方法时pos的值
let pos_up = this.pos
// 当指针尾不等于要跳过的目标字符串时,指针一直向后移动
while(this.tail.indexOf(stopTag)!==0 && this.pos<this.templateStr.length){ // && 后面的用来防止while死循环
this.pos++
// 改变尾巴为从当前指针这个字符,到剩下的全部字符
this.tail = this.templateStr.substr(this.pos)
}
// 返回扫描到的值
return this.templateStr.substring(pos_up, this.pos)
}
}
-
将解析出来的字符串变成tokens
渲染方法render()让模板字符串变成tokens数组(如下图);
折叠tokens,将#和/之间的数组整合起来作为子数组(利用collector收集器变量):
-
function parseTokens(tokens){
let res = [] // 存储最终返回结果
let sections = [] // 一个栈结构,来表示是否有嵌套数组
let collector = res // 收集器,用来收集嵌套数组,指向结果数组,因为数组是引用类型,所以collector和res指向同一个数组
for(let i=0; i<tokens.length; i++){
let token = token[i]
switch(token[0]){
case '#':
sections.push(token)
collector = token[2] = [] // 当有嵌套数组时将collector值初始化为目标收集的嵌套数组
break;
case '/':
sections.pop()
collector = sections.length>0?sections[sections.length-1][2]:res
break;
default:
collector.push(token) // 将每一项token存入collector数组
}
}
return res;
}
- 将tokens结合数据,解析为dom字符串
其中有一个问题是,js不能将带有点符号的数据(如 {{item.label}} )解析出来,所以需要编写lookup函数:
- 将tokens结合数据,解析为dom字符串
// lookup函数从一个对象中读取出嵌套的值,如下lookup(dataObj, 'a.b.c')值为123
let dataObj = {
a: {
b: {
c: 123
}
}
}
function lookup(dataObj, keyName){
// 如果keyName里面有点符号
if(keyName.indexOf('.')!==-1){
let keys = keyName.split('.')
let temp = dataObj
for(let i=0; i<keys.length; i++){
temp = temp[keys[i]]
}
return temp
}
return dataObj[keyName]
}
最后判断token[0]的值,若是"text"则与结果字符串直接拼接,若是"name"则取data中对应的值之后再拼接,若是"#"说明有嵌套数组则要递归。