手写mustache核心源码

模版引擎需要完成的两件事情

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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值