目录
模版引擎需要完成的两件事情
1、模版(字符串)变成token数组
2、将token数组解析成Dom
模版转token数组
token需要的信息
上一节我们知道了token需要哪些信息,主要包括:类型(text、name、#)内容 、子token(循环时才有)、开始索引、结束索引等等。我们以这四个为例进行demo演示。
类型:
text:如果是纯文本,类型为text。
name:如果是{{}}中间的,它从data中获取数据,类型为name。
#:如果是{{}}中间的且以#开头,表示这是一个循环,且在他的id为2的部分是这个循环的toen数组。类型为#。
内容:
text类型的内容:直接是文本
name类型的内容:直接是文本,后期需要去data获取数据
#类型的内容:{{}}中除去#的文本
token生成
思路
生成token需要遍历模版字符串,获取对应的信息。那么{{和}}成为关键的分割节点。我们的思路是:首先按照{{ 和 }}把模板字符串分割成一个类似token的数组,然后再针对#的循环做折叠处理。
捕获token信息, Scanner 类遍历模版字符串
Scanner 提供了两个函数:
1、scan:跳过该标识串,在这里的tag参数就是{{ 或者 }} ,让当前索引继续往前移到tag之后。
2、scanUtil :返回从当前下标到tag中间的字符串,并且让索引移到tag上
export default class Scanner{
constructor(str){
this.idx = 0;
this.str = str
this.tail = str
}
scan(tag){
if(this.tail.indexOf(tag) == 0){
this.idx += tag.length
this.tail = this.tail.substr(tag.length)
}
}
scanUtil(tag){
var backup = this.idx
while(this.tail.indexOf(tag) != 0 && this.idx < this.str.length){
this.idx ++
this.tail = this.tail.substr(1)
}
var res = this.str.substring(backup,this.idx)
return res
}
}
parseTemplateToTokens 函数 生成伪token数组
利用scanner类进行模版字符串的便利,生成token数组。
scanner.scanUtil(’{{’) 收集{{ 之前的数据 类型text
跳过{{ 收集 }}之前的数据 类型为name或者#或者/(#循环开始 /循环结束)
import Scanner from './Scanner'
import nestToken from './nestToken'
export default function parseToTokens(str){
var scanner = new Scanner(str)
var tokens = []
while(str.length > scanner.idx){
var tempidx = scanner.idx
// 收集{{之前的文本
var tempstr = scanner.scanUtil('{{')
if(tempstr){
tokens.push(['text',tempstr,tempidx,tempidx+tempstr.length])
}
scanner.scan('{{')
var tempidx = scanner.idx
// 收集{{}}之间的文本
var tempstr = scanner.scanUtil('}}')
if (tempstr){
if(tempstr[0] == '#'){
tokens.push(['#',tempstr.substr(1),tempidx,tempstr.length+tempidx])
}else if(tempstr[0] == '/'){
tokens.push(['/',tempstr.substr(1),tempidx,tempstr.length+tempidx])
}else{
tokens.push(['name',tempstr,tempidx,tempstr.length+tempidx])
}
}
scanner.scan('}}')
}
return nestToken(tokens) // 将生成的tokens进行折叠
}
将伪token数组进行折叠
思路:遇到name和text 直接插入 遇到# 循环折叠 并且将/之前的token插入#所在的token中。利用栈的概念。如果# 入栈,/出栈插入 。维护了一个收集器的指针。如果是循环中,插入栈顶#的子token数组 否者直接插入当前的数组。
export default function nestToken(tokens){
let nestTokens = [];
let sections = [];
let collector = nestTokens; // 待插入token的数组
for(let i = 0;i<tokens.length;i++){
let token = tokens[i]
switch(token[0]){
case '#':
token.splice(2,0,[])
console.log(token)
sections.unshift(token); // 入栈
collector = token[2] // 改变指针
break
case '/':
var section_pop = sections.shift() // 出栈
collector = sections.length > 0 ? sections[0][2] :nestTokens
console.log('jjjj',section_pop)
collector.push(section_pop) //插入
break
default:
collector.push(token)
}
}
return nestTokens
}
到这一步,我们的token数组就完成啦。
token数组转成DOM
lookup 函数 获取data数据
转成DOM的时候需去data中寻找数据,所以我们先写一个获取data数据的函数。
因为可能有{{a.b.c}}和{{.}}这种情况出现,所以还是需要特殊处理 不然data[a.b.c]这样取不到。
export default function lookup(obj,str){
if(str.indexOf('.')== -1) return obj[str]
if(str.length == 1) return obj
let arr = str.split('.');
return arr.reduce((p,c)=>{
return p[c]
},obj)
}
renderTemplate 函数 token渲染成DOM
将token渲染成dom,text直接追加,name获取数据追加,# 转换后追加
import lookup from './lookup'
import parseArray from './parseArray'
// 递归思路
export default function renderTemplate(data,tokens){
var res = ''
for(let i=0;i<tokens.length;i++){
if(tokens[i][0] == 'text'){
res += tokens[i][1]
}
else if(tokens[i][0] == 'name'){
res += lookup(data,tokens[i][1])
}
else if(tokens[i][0] == '#'){
res += parseArray(lookup(data,tokens[i][1]),tokens[i][2])
}
}
return res
}
parseArray 函数 转换循环的子token为dom
遍历data数据的length 然后进行渲染Dom
import renderTemplate from './renderTemplate'
export default function parseArray(data,token){
debugger
let res = ''
for(let i = 0;i<data.length;i++){
res += renderTemplate(data[i],token)
}
return res
}
index
window.SGG_TemplateEngine = {
// 渲染方法
render(str,data){
// 解析tokens
var tokens = parseToTokens(str)
// 生成DOM
var renderdom = renderTemplate(data,tokens)
console.log(renderdom)
return renderdom
}
}
var str = '<div>我爱{{somebody}},我喜欢吃的<ul>{{#like}}<li>有{{name}},他有这些成分{{#sr}}{{.}}{{/sr}}</li>{{/like}}</ul>,价格是{{a.b.c}}<div>'
var data = {
somebody:'我的',
like:[
{
name:'西红柿',
sr:['vc','维生素']
},
{
name:'西瓜',
sr:['vc','维生素']
}
],
a:{b:{c:'12'}}
}
window.SGG_TemplateEngine.render(str,data)