Javascript

Vue源码探秘之Mustache模版引擎

本文主要是介绍Vue源码探秘之Mustache模版引擎,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

theme: condensed-night-purple

Mustache学习笔记

什么是模版引擎?

将数据要变为视图的最优雅的解决方案。

模版引擎实现原理

1.将模版解析为tokens数组

2.将tokens数组替换数据后还原回模版字符串

代码 模版和数据

// 模版
let templateStr = `
  <ul>
    {{#students}}
        <li class="123">
            {{name}}爱好是:
            {{age}}
            <ol>
                {{#hobbies}}
                  <li>{{.}}</li>
                {{/hobbies}}
            </ol>
        </li>
    {{/students}}
</ul>`

// 数据
const data = {
    students: [{
        name: 'Yang',
        age: 23,
        hobbies: ['写代码', '吃肉', '减肥']
    }, {
        name: 'ZGR',
        age: 18,
        hobbies: ['吃肉', '吃肉', '吃肉']
    }, {
        name: 'test',
        age: 999,
        hobbies: ['唱', '跳', 'rap', '篮球']
    }]
}
/**
 * 通过Mustache.render方法完成2步
 * 1.把模版解析为tokens数组
 * 2.把tokens数组拼接数据后合并为模版字符串
 */
const domStr = Mustache.render(templateStr, data)

// 把拼接好的模版字符串写进body标签
document.body.innerHTML = domStr

分析=>将模版解析为tokens数组

// 全局提供Mustache
window.Mustache = {
    render(templateStr, data) {
        // 调用parseTemplateToTokens让模版字符串变成数组
        const tokens = parseTemplateToTokens(templateStr)
        // 调用renderTemplate函数把tokens变为Dom字符串
        return renderTemplate(tokens, data)
    }
}

1.首先要实现一个扫描器 可以扫描模版字符串

export default class Scanner {
    constructor(templateStr) {
        // 将模版字符串保存在自身
        this.templateStr = templateStr
        // 指针
        this.pointer = 0
        // 尾巴 一开始就是模版字符串的内容
        this.tail = templateStr
    }

    // 路过指定内容
    scan(tag) {
        if (this.tail.indexOf(tag) === 0) {
            // tag有多长 往后移动多少位 跳过标记
            this.pointer += tag.length
            // 改变尾巴
            this.tail = this.templateStr.substring(this.pointer)
        }
    }

    // 让指针进行扫描 直到遇见指定内容结束并且返回结束之前扫描的文字
    scanUntil(stopTag) {
        // 记录执行本方法时 pointer 的值
        const pointerBackUp = this.pointer
        // 当尾巴的开头不是stopTag的时候,就说明还没有扫描到
        while (!this.eos() && this.tail.indexOf(stopTag) !== 0) {
            this.pointer++
            this.tail = this.templateStr.substring(this.pointer)
        }
        return this.templateStr.substring(pointerBackUp, this.pointer)
    }

    // 指针是否到头了
    eos() {
        // 当前的指针大于等于templateStr的长度 说明已经到头了
        return this.pointer >= this.templateStr.length
    }
}

2.通过扫描器实现分割模版字符串为tokens

export default function parseTemplateToTokens(templateStr) {
    // tokens数组
    const tokens = []
    // 创建扫描器
    const scanner = new Scanner(templateStr)
    // 标记前的内容
    let words
    // 启动扫描器
    while (!scanner.eos()) {
        // 收集开始标记
        words = scanner.scanUntil('{{')
        // 不为空存储
        words && tokens.push(['text', words])
        // 跳过本次开始标记
        scanner.scan('{{')
        // 收集结束标记
        words = scanner.scanUntil('}}')
        // 不为空存储
        if (words !== '') {
            // 如果以#开头
            if (words[0] === '#') {
                // 存储类型为# 值为去掉#的字符串
                tokens.push(['#', words.substring(1)])
            } else if (words[0] === '/') {
                // 存储类型为/ 值为去掉/的字符串
                tokens.push(['/', words.substring(1)])
            } else {
                // 如果均不是 保存类型为 name
                tokens.push(['name', words])
            }
        }
        // 跳过本次结束标记
        scanner.scan('}}')
    }
    // 返回处理好的tokens数组 此时tokens是零散的数组
    return nestTokens(tokens)
}

3.把零散的tokens解析为多维数组

export default function nestTokens(tokens) {
    // 结果数组
    const nestedTokens = []
    // 收集器 最初指向 结果数组
    let collector = nestedTokens
    // 存放小tokens
    const sections = []
    // 遍历零散数组
    tokens.forEach(token => {
        // 判断token第0位
        switch (token[0]) {
            case '#':
                // 收集器中放入这个token
                collector.push(token)
                // 入栈
                sections.push(token)
                // 换收集器 给token第二项添加一个空数组 并且让收集器指向它
                collector = token[2] = []
                break
            case '/':
                // 出栈
                sections.pop()
                // 改变收集器为栈结构队尾数组下标为2的数组
                collector = sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens
                break
            default :
                collector.push(token)
                break
        }
    })
    // 返回结果 此时数据已经处理完毕
    return nestedTokens
}

此时已经实现了第一步把模版字符串变成tokens数组

分析=>将tokens数组解析回模版字符串

1.实现renderTemplate此方法就是为了把tokens数组解析回模版字符串

export default function renderTemplate(tokens, data) {
    // 结果字符串
    let resultStr = ''
    // 遍历tokens
    tokens.forEach(token => {
        // 如果当前的类型是text
        if (token[0] === 'text') {
            // 直接+=第二位就好了
            resultStr += token[1]
            // 如果当前的类型是name
        } else if (token[0] === 'name') {
            // 那么用lookUp拿出值
            resultStr += lookUp(data, token[1])
        } else if (token[0] === '#') {
            resultStr += parseArray(token, data)
        }
    })
    return resultStr
}

2.实现lookUp方法 此方法为了实现利用a.b.c的字符串从数据中取值

export default function lookUp(dataObj, keyName) {
    // 如果keyName有. 并且不能只有.
    if (keyName && keyName !== '.' && keyName.indexOf('.') !== -1) {
        // 分割字符串
        const keys = keyName.split('.')
        // 临时变量保存数据
        let temp = dataObj
        keys.forEach(key => {
            // 一层一层找出数据
            temp = temp[key] ? temp[key] : ''
        })
        // 返回数据
        return temp
    }
    // 如果没有.符号
    return dataObj[keyName] || ''
}

3.实现 parseArray 实现递归

export default function parseArray(token, data) {
    let v = lookUp(data, token[1])
    let resultStr = ''
    v.forEach(item => {
        // 要判断.
        resultStr += renderTemplate(token[2], {...item, '.': item})
    })
    return resultStr
}

此时模版字符串已经准备就绪 通过innerHTML写进body里就可以了

完整代码:https://gitee.com/ly-glimmer/vue-source-code-analysis

这篇关于Vue源码探秘之Mustache模版引擎的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!