Vue 源码学习 —— Mustache 模板引擎原理

前言

  • Mustache 是模板引擎思想的奠基者,Vue 中的模板引擎也借用了它的思想
  • 因此,在学习Vue的模板引擎之前,先学习Mustache能加更容易理解模板引擎的设计思想

Mustache

  • 下载:到 BootCDN 搜索 mustache,复制链接到浏览器打开,并保存到本地即可
  • 或使用该链接:mustache.js(v4.1.0)
  • 引入
    Vue 源码学习 —— Mustache 模板引擎原理_第1张图片
    Vue 源码学习 —— Mustache 模板引擎原理_第2张图片
  • 基本使用
  • 渲染普通对象:
 <div id="root">div>
 <script src="js/mustache.js">script>
 <script>
     let template = `
         
  • {{name}}的信息

    姓名:{{name}}

    年龄:{{age}}

    性别:{{sex}}

`
let data = { name: 'aa', age: 12, sex: '女' } let DOMStr = Mustache.render(template, data) let root = document.getElementById('root') root.innerHTML = DOMStr
script>

Vue 源码学习 —— Mustache 模板引擎原理_第3张图片

  • 渲染普通数组:
 <div id="root">div>
 <script src="js/mustache.js">script>
 <script>
     let template = `
         
    {{#arr}}
  • {{.}}
  • {{/arr}}
`
let data = { arr: ['A', 'B', 'C'] } let DOMStr= Mustache.render(template, data) let root = document.getElementById('root') root.innerHTML = DOMStr
script>

Vue 源码学习 —— Mustache 模板引擎原理_第4张图片

  • 渲染对象数组:
 <div id="root">div>
 <script src="js/mustache.js">script>
 <script>
     let template = `
         
    {{#arr}}
  • {{name}}的信息

    姓名:{{name}}

    年龄:{{age}}

    性别:{{sex}}

  • {{/arr}}
`
let data = { arr: [ { name: 'aa', age: 12, sex: '女' }, { name: 'bb', age: 16, sex: '男' }, { name: 'cc', age: 17, sex: '男' }, ] } let DOMStr= Mustache.render(template, data) let root = document.getElementById('root') root.innerHTML = DOMStr
script>

Vue 源码学习 —— Mustache 模板引擎原理_第5张图片

  • 渲染复杂嵌套情况:
 <div id="root">div>
 <script src="js/mustache.js">script>
 <script>
     let template = `
         
    {{#arr}}
  • {{name}}的爱好是:
      {{#hobbies}}
    1. {{.}}
    2. {{/hobbies}}
  • {{/arr}}
`
let data = { arr: [ {name: 'aa', age: 12, hobbies: ['swim', 'music']}, {name: 'bb', age: 16, hobbies: ['run', 'animation']}, {name: 'cc', age: 17, hobbies: ['joggy', 'movie']} ] } let templateStr = Mustache.render(template, data) let root = document.getElementById('root') root.innerHTML = templateStr
script>

Vue 源码学习 —— Mustache 模板引擎原理_第6张图片

  • 冷知识:在没有ES6模板语法之前,可以这样去写模版 (相比字符串拼接更优雅)
 <div id="root">div>
 <script src="js/mustache.js">script>
 
 
 
 <script type="text/template" id="template">
     <div>
         {{#show}}
             <p>show</p>
         {{/show}}
         {{#hidden}}
             <p>hidden</p>
         {{/hidden}}
     </div>
 script>
 <script>
     let template = document.getElementById('template').innerText

     let data = {
         show: true,
         hidden: false
     }

     let templateStr = Mustache.render(template, data)

     let root = document.getElementById('root')
     root.innerHTML = templateStr
 script>

Mustache 原理

  • Mustache 会将模版字符串先编译成 tokens,然后再结合数据解析成 DOM 字符串
    Vue 源码学习 —— Mustache 模板引擎原理_第7张图片
  • tokens 是一个嵌套的 js 数组,也是抽象语法树 AST
    Vue 源码学习 —— Mustache 模板引擎原理_第8张图片
  • 当模版存在循环时:
    Vue 源码学习 —— Mustache 模板引擎原理_第9张图片
  • 模板存在双重循环时:
    Vue 源码学习 —— Mustache 模板引擎原理_第10张图片

手写简单源码

  • 目录结构
    Vue 源码学习 —— Mustache 模板引擎原理_第11张图片
  • index.js
import parseTemplateToTokens from './parseTemplateToTokens'
import renderTemplate from './renderTemplate'
window.Mustache = {
    render(templateStr, data) {
        let tokens = parseTemplateToTokens(templateStr)
        let DOMStr = renderTemplate(tokens, data)
        console.log(DOMStr)
    }
}

// 测试用例
let templateStr = `
    
{{#arr}}

{{name}}

{{age}}

{{sex}}

爱好:

    {{#fav}}
  • {{.}}
  • {{/fav}}
{{/arr}}
`
let data = { arr: [ { name: 'aa', age: 12, sex: '女', fav: ['movie', 'skr'] }, { name: 'bb', age: 16, sex: '男', fav: ['study', 'icecream'] }, { name: 'cc', age: 17, sex: '男', fav: ['swim', 'sing'] } ] } window.Mustache.render(templateStr, data)
  • parseTemplateToTokens.js
import Scanner from './Scanner'
import nestTokens from './nestTokens'

export default function parseTemplateToTokens (templateStr) {
    let tokens = []
    let scanner = new Scanner(templateStr)
    let words
	// 扫描未结束
    while (!scanner.eos()) {
    	// 扫描并返回遇到 '{{' 之前的字符
        words = scanner.scanUntil('{{')
        if (words) {
        	// 存入 tokens,并且 '{{' 之前的字符为普通文本 即 text
            tokens.push(['text', words])
        }
        // 跳过 '{{'
        scanner.scan('{{')
        // 扫描并返回遇到 '}}' 之前的字符
        words = scanner.scanUntil('}}')
        // '{{' 和 '}}' 之间的字符为约定的语法格式,需要进行类型判断
        if (words) {
            if (words.startsWith('#')) {	// 循环开始标记
                tokens.push(['#', words.substring(1)])

            } else if (words.startsWith('/')) {	// 循环结束标记
                tokens.push(['/', words.substring(1)])

            } else {	// 变量标记
                tokens.push(['name', words])
            }
        }
        // 跳过 '}}'
        scanner.scan('}}')
    }
    // 将 tokens 形成嵌套的数组结构
    tokens = nestTokens(tokens)

    return tokens
}
  • Scanner.js
export default class Scanner {
    constructor (templateStr) {
        this.templateStr = templateStr
        this.tail = templateStr
        this.pos = 0
    }
	// 扫描并跳过
    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            this.pos += tag.length
            this.tail = this.templateStr.substring(this.pos)
        }
    }
	// 扫描并返回
    scanUntil (stopTag) {
        const pos_backup = this.pos
        while (!this.eos() && this.tail.indexOf(stopTag) != 0) {
            this.pos++
            this.tail = this.templateStr.substring(this.pos)
        }
        return this.templateStr.substring(pos_backup, this.pos)
    }
	// 是否扫描结束
    eos () {
        return this.pos >= this.templateStr.length
    }
}
  • nestTokens.js
// 利用 #、/ 作为入栈和出栈的标志,嵌套tokens
export default function nestTokens (tokens) {
    let nestedTokens = []
    let stack = []
    stack.push(nestedTokens)
    for (let i=0; i<tokens.length; i++) {
        let token = tokens[i]

        switch (token[0]) {
            case '#':
                token[2] = []
                stack[stack.length-1].push(token)
                stack.push(token[2])

                break
            case '/':
                stack.pop()
                break
            default:
                stack[stack.length-1].push(token)
        }
    }
    return nestedTokens
}
  • renderTemplate.js
// 递归的将 tokens 转换成 DOMStr
export default function renderTemplate (tokens, data) {
    let DOMStr = ''
    for (let i=0; i<tokens.length; i++) {
        let token = tokens[i]
        switch (token[0]) {
            case 'text':
                DOMStr += token[1]
                break
            case 'name':
                token[1] === '.' 
                    ? DOMStr += data
                    : DOMStr += data[token[1]]
                break
            case '#':
                for (let i=0; i<data[token[1]].length; i++) {
                    let depData = data[token[1]][i]
                    DOMStr += renderTemplate(token[2], depData)
                } 
        }
    }
    return DOMStr
}
  • 结果
    Vue 源码学习 —— Mustache 模板引擎原理_第12张图片

你可能感兴趣的:(Vue,源码,vue)