简易版mustache的实现

本文档详细介绍了如何实现一个简易版的Mustache模板引擎,包括Scanner、parseTempalteToTokens、nestTokens、renderTemplate等核心方法,以及lookup和parseArray辅助函数。通过这些函数,实现了模板字符串与数据的结合,支持#(循环)、/(闭合循环)、name(变量)等基本功能。示例展示了如何处理嵌套的数据结构并输出结果。

简易版mustache的实现

  • 最近在学习mustache的实现,以下简单模拟了一版mustache
  • 实现效果如下:
    // 通过render方法,实现数据以及模板的整合(也是一个字符串)
    // templateStr(模板字符串),data(数据)
    var templateStr = '这是一个{{infor}}数据,{{infor}}数据在这,{{a.b.c}}'
    var data = {
               infor:'模拟',
               a:{
                   b:{
                       c:100
                   }
               }
           }
    MyMustAche.render(templateStr,data)
    // 得到结果 这是一个模拟数据,模拟数据在这,100
    

该简易包目录结构如下

  • index.js 入口文件
  • lookup.js // 该函数功能是通过连续点符号,找到该属性
  • nestTokens.js // 折叠tokens,把一维的tokens数组,折叠成具有循环结构的多维tokens数组
  • parseArray.js // 遍历tokens每一项时,如果遇到toekn为数组的话,通过该函数处理。
  • parseTempalteToTokens.js // 该方法将模板字符串变为tokens数组
  • renderTemplate.js // 把折叠号的tokens转为最后处理过的字符串
  • Scanner.js // 找到 {{ 以及 }} 字符的索引 以及之前 之后的字符串,通过parseTempalteToTokens整合为tokens数组

index.js


// import Scanner from './Scanner'
import parseTempalteToTokens from './parseTempalteToTokens'
import renderTemplate from './renderTemplate'
window.MyMustAche = {
    render(templateStr,data){
    	// 通过parseTempalteToTokens转为tokens
        var tokens = parseTempalteToTokens(templateStr)
        // 调用renderTemplate函数,让tokens数组变为dom字符串
        var domStr = renderTemplate(tokens,data)
        return domStr
    }
}

parseTempalteToTokens


import Scanner from './Scanner.js'
import nestTokens from './nestTokens'
// 将模板字符串变为tokens数组
export default function parseTempalteToTokens(){
    var tokens = []

    // 创建扫描器
    var scanner = new Scanner(templateStr)
    var words;
    // 让扫描器工作
    while (!scanner.eos()) {
        // 收集开始标记出现之前的文字
        words = scanner.scanUtil('{{')
        if(words!=''){
            // 去掉空格 不能去除标签中的空格,智能去除文本中的空格
            var isInJJH = false
            var _words =''
            for (let i = 0; i < words.length; i++) {
                if(words[i]=="<"){
                    isInJJH = true
                } else if(words[i]=='>'){
                    isInJJH = false
                }
                if(!/\s/.test(words[i])){
                    _words += words[i]
                } else {
                    if(isInJJH){
                        _words += ' '
                    }
                }
                
            }
            tokens.push(['text',_words])
        }
        // 过}}
        scanner.scan('{{')
        words = scanner.scanUtil('}}')
        if(words!=''){
            // 现在的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
    // 当前的tokens是一个一维数组
	// 通过nestTokens处理成具有循环结构的二维数组
    return nestTokens(tokens)
}

没有通过nestTokens处理的tokens
没有通过nestTokens处理的tokens

通过nestTokens处理的数组通过nestTokens处理的数组

Scanner

export default class Scanner {
    constructor(templateStr){
        // 将templateStr写入this
        this.templateStr = templateStr
        // 指定,当前位置,默认为0
        this.pos = 0;
        // 尾巴,一开始就是整个模板字符串
        this.tail = templateStr
    }
    // 就是走过指定内容,没有返回值
    scan(tag){
        if(this.tail.indexOf(tag)==0){
            // tag有多长,比如tag是{{,那就让pos后移几位
            this.pos += tag.length
            // 尾巴后移
            this.tail = this.templateStr.substring(this.pos)
        }
    }
    // 让指针进行扫描,直到遇到指定内容结束,并且返回结束之前扫描路过的文字
    scanUtil(stopTag){
        // 记录一下执行本方法的时候的pos的值
        const pos_backup = this.pos
        // 当尾巴的开头不是stopTag的时候,就说明还没扫描到stopTag
        while (!this.eos() && this.tail.indexOf(stopTag) != 0 ) {
            this.pos ++;
            this.tail = this.templateStr.substring(this.pos)
        }
        // 返回的上一次pos和这一次pos之间的数据
        return this.templateStr.substring(pos_backup,this.pos)
    }
    // 指针是否已经到头,返回布尔值
    eos(){
        return this.pos>=this.templateStr.length
    }
}

nestTokens

// 函数的功能就是折叠tokens,将#和/ 之间的tokens整合起来,
// 因为# 和 / 之间是循环的逻辑(在这个简易的mustache包中)

export default function nestTokens(tokens){
    var nestTokens = []
    // 栈结构,存放小的tokens
    // 栈顶(靠近端口的,最新进入的)tokens数组中当前操作的tokens小数组
    var sections = []
    // 收集器, 天生指向nestTokens结果数组,引用类型值,
    // 所以指向的是同一个数组
    
    var collector = nestTokens;
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        switch (token[0]) {
            case '#':
                // 收集器中放入token
                collector.push(token)
                // 入栈
                sections.push(token)
                // 收集器改变
                // 给token添加下标为2的项,并让收集器指向他
                collector = token[2] = []
                break;
            case '/':
                // 出栈,pop会返回刚刚弹出的项
                sections.pop()
                // 改变收集器为栈结构队尾那项的下标为2的数组
                collector = sections.length>0?sections[sections.length-1][2]:nestTokens
                
                break;
            default:
                collector.push(token)
                break;
        }
    }
    // 返回折叠的tokens
    return nestTokens
}

renderTemplate

import lookup from './lookup'
import parseArray from './parseArray'
export default function renderTemplate(tokens,data) {
    // 结果字符串
    var resulteStr = ''
    // 遍历tokens
    for (let i = 0; i < tokens.length; i++) {
        const token = tokens[i];
        //看类型
        if(token[0] == 'text'){
            //如果是text类型,不处理
            resulteStr += token[1]
        } else if(token[0]=='name'){
            // 如果是name,直接拿数据
            resulteStr +=lookup(data,token[1]) 
        } else if(token[0]=='#'){
            // 如果是#代表是数组,要递归
            resulteStr += parseArray(token,data)
        }
    }
    return resulteStr
}

lookup


// 该函数功能是在dataObj中,可以通过连续点符号的keyName属性,比如
// obj = {
//     a:{
//         b:{
//             c:100
//         }
//     }
// }
// 要识别出a.b.c = 100
export default function lookup(dataObj,keyName){
    if(keyName.indexOf('.')!='-1'&&keyName !=  '.'){
        var keys = keyName.split('.');
        var temp = dataObj;
        for (let i = 0; i < keys.length; i++) {
            temp = temp[keys[i]];
        }
        return temp
    }
    return dataObj[keyName]
}

parseArray

// 处理数组
// 这个函数接收的是token,而不是tokens

import lookup from "./lookup";
import renderTemplate from "./renderTemplate";

// 这里的token就是['#',studengs:[]]形式
export default function parseArray(token,data){
    var v =lookup(data,token[1]);
    var resultStr = ''
    for (let i = 0; i < v.length; i++) {
        // 这里要补一个.的识别
        // 因为模板在使用# / 循环的时候,可以使用.表示当前项的数据
        resultStr += renderTemplate(token[2],{
            ...v[i],
            '.':v[i]
        })
        
    }
    return resultStr
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值