【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现

文章目录

  • 1. 模板引擎的介绍
    • 1.1 模板引擎是什么?
    • 1.2 模板引擎是怎么来的?(发展历史)
      • 1. 使用原生的DOM操作
      • 2. 使用数组中的join方法
      • 3. 使用ES6反引号的方法
  • 2. mustache基本使用
    • 2.1 mustache 库的简介
      • 引入 mustache 库
      • mustache 的模板语法
      • 1. 最简单的情况——不循环对象数组
      • 2. 循环最简单的数组
      • 3. 循环对象数组 (v-for类似)
      • 4. 循环嵌套对象数组和简单数组
      • 5. 控制元素的显示与隐藏——布尔值
      • 6. `script` 模板写法
  • 3. mustache的原理
    • 3.1 replace()方法 和 正则表达式实现最简单的模板数据填充
      • 预备知识
        • replace()方法
        • 正则的捕获
      • 实现
    • 3.2 mustache 的实现原理
    • 3.3 什么是 tokens?
      • 1. 最简单的形式
      • 2. 循环数组情况下的 tokens
      • 3. 多重循环
    • 3.4 实现 mustache 库 的重点是
  • 4. 手写实现mustache库
    • 4.1 配置webpack环境
    • 4.2 实现 Scanner 扫描器类
      • 预备知识 JS中字符串提取字串的方法
      • src/Scanner.js
      • www/index.html
      • src/index.js
    • 4.3 生成 tokens 数组
      • 4.3.1 完成简单的一层数组
        • src/parseTemplateToTokens
        • www/index.html
        • src/index.js
      • 4.3.2 完成嵌套数组(重难点)
        • src/nestTokens.js
    • 4.4 将tokens解析为DOM字符串
      • index.html
      • src/index.js
      • src/lookup.js 可以在对象中,寻找连续点符号的属性
      • 简化版 src/lookup.js
      • src/renderTemplate.js
      • src/parseArray.js 递归调用 renderTemplate
    • 4.5 完善空格问题

来自尚硅谷的课程笔记 课程链接[尚硅谷邵山欢(考拉老师)Vue之mustache模板引擎]
加入大量的注释以及改写

1. 模板引擎的介绍

1.1 模板引擎是什么?

模板引擎是将数据data变为视图view(html)的解决方案

数据:
【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第1张图片
视图:
【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第2张图片
Vue的解决方案

<li v-for="item in arr">li>

1.2 模板引擎是怎么来的?(发展历史)

【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第3张图片

1. 使用原生的DOM操作


<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>01_数据变为视图-纯DOM法title>
head>

<body>
  <ul id="list">ul>
  
  <script>
    var arr = [
      { name: '小明', age: 12, sex: '男' },
      { name: '小红', age: 11, sex: '女' },
      { name: '小强', age: 13, sex: '男' },
    ]
    
    var list = document.getElementById('list')

    for (let i = 0; i < arr.length; i++) {
      // 每遍历一项,都要用 DOM 方法去创建 li 标签
      let oLi = document.createElement('li')
      
      // 创建 hd 这个 div
      let hdDiv = document.createElement('div')
      hdDiv.className = 'hd'
      hdDiv.innerText = arr[i].name + '的基本信息'
      oLi.appendChild(hdDiv)

      // 创建 bd 这个 div
      let bdDiv = document.createElement('div')
      bdDiv.className = 'bd'
      oLi.appendChild(bdDiv)
      
      // 创建 3 个 p 标签
      let p1 = document.createElement('p')
      p1.innerText = '姓名:' + arr[i].name
      bdDiv.appendChild(p1)
      let p2 = document.createElement('p')
      p2.innerText = '年龄:' + arr[i].age
      bdDiv.appendChild(p2)
      let p3 = document.createElement('p')
      p3.innerText = '性别:' + arr[i].sex
      bdDiv.appendChild(p3)
      
      // 创建的节点是孤儿节点,所以必须要上树才能让用户看见
      list.appendChild(oLi)
    }
  script>

body>

html>

2. 使用数组中的join方法


<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>02_数据变为视图-数组join法title>
head>

<body>
  <ul id="list">
  ul>
  <script>
    var arr = [
      { name: '小明', age: 12, sex: '男' },
      { name: '小红', age: 11, sex: '女' },
      { name: '小强', age: 13, sex: '男' },
    ]
    var list = document.getElementById('list')
    
    // 遍历 arr 数组,每遍历一项,就以字符串的视角将HTML字符串添加到list中
    for (let i = 0; i < arr.length; i++) {
      list.innerHTML += [
        '
  • ', '
    ' + arr[i].name + '的信息
    '
    , '
    ', '

    姓名:' + arr[i].name + '

    '
    , '

    年龄:' + arr[i].age + '

    '
    , '

    性别:' + arr[i].sex + '

    '
    , '
    '
    , '
  • '
    ].join('') }
    script> body> html>

    3. 使用ES6反引号的方法

    
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>03_数据变为视图-ES6反引号法title>
    head>
    
    <body>
      <ul id="list">
      ul>
      <script>
        var arr = [
          { name: '小明', age: 12, sex: '男' },
          { name: '小红', age: 11, sex: '女' },
          { name: '小强', age: 13, sex: '男' },
        ]
        var list = document.getElementById('list')
        // 遍历 arr 数组,每遍历一项,就以字符串的视角将HTML字符串添加到list中
        for (let i = 0; i < arr.length; i++) {
          list.innerHTML += `
            
  • ${arr[i].name}的基本信息

    姓名:${arr[i].name}

    年龄:${arr[i].age}

    性别:${arr[i].sex}

  • `
    }
    script> body> html>

    2. mustache基本使用

    2.1 mustache 库的简介

    • mustache 官方 git:https://github.com/janl/mustache.js
    • mustache 中文翻译是 “胡子”
    • mustache 是最早的模板引擎库,非常有创造性的、轰动性的

    引入 mustache 库

    <script src="https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js">script>
    

    mustache 的模板语法

    <ul>
    {{#arr}}
      <li>
        <div class="hd">{{name}}的基本信息div>
        <div class="bd">
          <p>姓名:{{name}}p>
          <p>年龄:{{age}}p>
          <p>性别:{{sex}}p>
        div>
      li>
    {{/arr}}
    ul>
    

    1. 最简单的情况——不循环对象数组

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第4张图片

      <div id="container">div>
      <h1>h1>
      <script>
        var templateStr = `
          

    我买了一个{{thing}},好{{mood}}啊

    `
    var data = { thing: '华为手机', mood: '开心' } var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
    script>

    2. 循环最简单的数组

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第5张图片

      <div id="container">div>
      <h1>h1>
      <script>
        var templateStr = `
          
      {{#arr}}
    • {{.}}
    • {{/arr}}
    `
    var data = { arr: ['苹果', '梨子', '香蕉'] } var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
    script>

    3. 循环对象数组 (v-for类似)

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第6张图片

      <div id="container">div>
      <script>
        var templateStr = `
          
      {{#arr}}
    • {{name}}的基本信息

      姓名:{{name}}

      年龄:{{age}}

      性别:{{sex}}

    • {{/arr}}
    `
    var data = { arr: [ { name: '小明', age: 12, sex: '男' }, { name: '小红', age: 11, sex: '女' }, { name: '小强', age: 13, sex: '男' }, ] } var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
    script>

    4. 循环嵌套对象数组和简单数组

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第7张图片

      <div id="container">div>
      <h1>h1>
      <script>
        var templateStr = `
          
      {{#arr}}
    • {{name}}的爱好是:
        {{#hobbies}}
      1. {{.}}
      2. {{/hobbies}}
    • {{/arr}}
    `
    var data = { arr: [ { name: '小明', age: 12, hobbies: ['游泳', '羽毛球'] }, { name: '小红', age: 11, hobbies: ['编程', '写作文', '看报纸'] }, { name: '小强', age: 13, hobbies: ['打台球'] } ] } var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
    script>

    5. 控制元素的显示与隐藏——布尔值

    true显示
    false不显示
    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第8张图片

      <div id="container">div>
      <h1>h1>
      <script>
        var templateStr = `
        {{#m}}
          

    哈哈哈

    {{/m}} `
    var data = { m: true } var domStr = Mustache.render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
    script>

    6. script 模板写法

    scirpt标签中写入模板,只要type的值不是text/javascript,都不会被当作js执行解析,这样可以在script标签中写入模板,可以高亮可以自动填充

      <div id="container">div>
    
      
      <script type="text/template" id="mytemplate">
        <ul id="list">
            {{#arr}}
            <li>
              <div class="hd">{{name}}的基本信息</div>
              <div class="bd">
                <p>姓名:{{name}}</p>
                <p>年龄:{{age}}</p>
                <p>性别:{{sex}}</p>
              </div>
            </li>
            {{/arr}}
          </ul>
      script>
    
      <script>
        var templateStr = document.getElementById('mytemplate').innerHTML
        var data = {
          arr: [
            { name: '小明', age: 12, sex: '男' },
            { name: '小红', age: 11, sex: '女' },
            { name: '小强', age: 13, sex: '男' },
          ]
        }
        var domStr = Mustache.render(templateStr, data)
        var container = document.getElementById('container')
        container.innerHTML = domStr
      script>
    

    3. mustache的原理

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第9张图片

    3.1 replace()方法 和 正则表达式实现最简单的模板数据填充

    预备知识

    replace()方法

    这个方法接收两个参数,第一个参数可以是一个RegExp对象或一个字符串(这个字符串不会转换为正则表达式),第二个参数可以是一个字符串或一个函数

    replace()的第二个参数可以是一个函数。在只有一个匹配项时,这个函数会收到3个参数:与整个模式匹配的字符串、匹配项在字符串中的开始位置,以及整个字符串

    console.log('我爱踢足球,我爱脱口秀'.replace(/我/g, function (a, b, c) {
      console.log(a, b, c)
      return '你'
    }))
    

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第10张图片

    正则的捕获

    /\}\}{(\w+)\}\}/ 
    

    表示捕获{{}}中间的多个文字或数字

    var templateStr = '

    我买了一个{{thing}},好{{mood}}的啊

    '
    console.log(templateStr.replace(/\{\{(\w+)\}\}/g, function (a, b, c, d) { console.log(a, b, c, d) return '*' }))

    在这里插入图片描述

    实现

    <div id="container">div>
    
    <script>
      var templateStr = '

    我买了一个{{thing}},花了{{money}},好{{mood}}

    '
    var data = { thing: '华为手机', money: 5999, mood: '开心' } // 最简单的模板引擎实现机理,利用的是正则表达式中的 replace() 方法 // replace() 的第二个参数可以是一个函数,这个函数提供捕获的东西的参数,就是 $1 结合data对象,即可进行智能的替换 // function中的参数分别是:①findStr匹配到的部分{{thing}} ②捕获到的thing ③位置9 ④原串 function render(templateStr, data) { return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) { return data[$1] }) } var domStr = render(templateStr, data) var container = document.getElementById('container') container.innerHTML = domStr
    script>

    3.2 mustache 的实现原理

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第11张图片

    3.3 什么是 tokens?

    tokens 是JS的嵌套数组,就是模板字符串的JS表示
    它是抽象语法树虚拟节点等的开山鼻祖

    1. 最简单的形式

    模板字符串

    <h1>我买了一个{{thing}},好{{mood}}啊h1>
    

    tokens

    [
      ["text", "

    我买了一个"], ["name", "thing"], ["text", "好"], ["name", "mood"], ["text", "啊

    "
    ] ]

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第12张图片

    2. 循环数组情况下的 tokens

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第13张图片

    3. 多重循环

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第14张图片

    3.4 实现 mustache 库 的重点是

    1. 模板字符串编译为 tokens
    2. tokens 结合数据data,解析为 DOM 字符串

    4. 手写实现mustache库

    4.1 配置webpack环境

    使用 webpack 和 webpack-dev-server 构建
    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第15张图片
    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第16张图片

    新建目录 YK_TemplateEngine

    cd YK_TemplateEngine
    
    npm init -yes
    
    cnpm install -D webpack@4 webpack-cli@3 webpack-dev-server@3
    

    新建 webpack.config.js 文件 设置代码如下

    const path = require("path");
    
    module.exports = {
      mode: "development",
      // 入口
      entry: "./src/index.js",
      // 出口
      output: {
        filename: "bundle.js",
      },
      // 配置webpack-dev-server
      devServer: {
        // 静态根目录
        contentBase: path.join(__dirname, "www"),
        // 不压缩
        compress: false,
        // 端口号
        port: 8080,
        // 虚拟打包的路径,bundle.js文件没有真正生成
        publicPath: "/xuni/",
      },
    };
    

    新建 src/index.js

    alert('nihao')
    

    新建 www/index.html

    
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    
    <body>
      <h1>我是index.htmlh1>
      <script src="xuni/bundle.js">script>
    body>
    
    html>
    

    package.json 文件中新增命令:

    {
      "scripts": {
        "dev": "webpack-dev-server",
      }
    }
    

    终端运行 npm run dev
    访问:http://localhost:8080/http://127.0.0.1:8080/xuni/bundle.js, 可以看到 www/index.htmlxuni/bundle.js 文件的内容
    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第17张图片
    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第18张图片

    4.2 实现 Scanner 扫描器类

    预备知识 JS中字符串提取字串的方法

    ECMAScript提供了3个从字符串中提取子字符串的方法:slice()substr()substring()
    这3个方法都返回调用它们的字符串的一个子字符串,而且都接收一或两个参数。
    第一个参数表示子字符串开始的位置,第二个参数表示子字符串结束的位置
    slice()substring()而言,第二个参数是提取结束的位置(即该位置之前的字符会被提取出来)。
    substr()而言,第二个参数表示返回的子字符串数量
    任何情况下,省略第二个参数都意味着提取到字符串末尾。
    concat()方法一样,slice()substr()substring()不会修改调用它们的字符串,而只会返回提取到的原始新字符串值

    src/Scanner.js

    /**
     * 扫描器类
     */
    export default class Scanner {
      constructor(tempalteStr) {
        this.tempalteStr = tempalteStr;
        // 指针
        this.pos = 0;
        // 尾字符串,一开始是模板字符串原文
        this.tail = tempalteStr;
      }
      // 扫描走过指定内容{{或}},没有返回值
      scan(tag) {
        if (this.tail.indexOf(tag) === 0) {
          // tag 有多长,比如“{{”长度是2,就让指针向后移动多少位
          this.pos += tag.length;
          this.tail = this.tempalteStr.substr(this.pos);
        }
      }
      // 让指针进行扫描,直到遇到指定{{或}}内容结束,返回结束之前路过的文字
      scanUtil(stopTag) {
        // 记录开始执行时 pos 的值
        const post_backup = this.pos;
        // 当尾字符串的开头不是stopTag时,说明还没有扫描到stopTag && 寻找到最后找不到
        while (!this.eos() && this.tail.indexOf(stopTag) !== 0) {
          this.pos++;
          // 改变尾字符串 从当前指针到最后的全部字符
          this.tail = this.tempalteStr.substr(this.pos);
        }
        return this.tempalteStr.substring(post_backup, this.pos);
      }
      // 判断指针是否已经到头 end of string 到头了
      eos() {
        return this.pos >= this.tempalteStr.length
      }
    }
    

    www/index.html

    
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    
    <body>
      <h1>你好!!!h1>
      <script src="xuni/bundle.js">script>
      <script>
        var templateStr = '

    我买了一个{{thing}},好{{mood}}啊

    '
    var data = { thing: '华为手机', mood: '开心' } SGG_TemplateEngine.render(templateStr, data)
    script> body> html>

    src/index.js

    import Scanner from "./Scanner";
    
    // 全局提供YK_TemplateEngine对象
    window.YK_TemplateEngine = {
      // 渲染方法
      render(tempalteStr, data) {
        // 实例化一个扫描器,构造时提供一个参数,参数就是模板字符串
        // 也就是这个扫描器就是针对这个模板字符串工作的
        var scanner = new Scanner(tempalteStr);
        var word
        // pos指针没有到头
        while (!scanner.eos()) {
          word = scanner.scanUtil("{{");
          console.log(word+'***');
          scanner.scan("{{");
          word = scanner.scanUtil("}}");
          console.log(word);
          scanner.scan("}}");
        }
      },
    };
    

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第19张图片

    4.3 生成 tokens 数组

    4.3.1 完成简单的一层数组

    src/parseTemplateToTokens

    import Scanner from "./Scanner";
    /**
     * 将模板字符串转换成tokens数组
     */
    export default function parseTemplateToTokens(tempalteStr) {
      var tokens = [];
      // 创建扫描器
      var scanner = new Scanner(tempalteStr);
      var words
      // 让扫描器工作
      while (!scanner.eos()) {
        // 收集开始标记出现之前的文字
        words = scanner.scanUtil("{{");
        if (words !== '') {
          tokens.push(['text', words])
        }
        scanner.scan("{{");
        // 收集
        words = scanner.scanUtil("}}");
        if (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("}}");
      }
      return tokens;
    }
    

    www/index.html

    // 模板字符串
    // var tempalteStr = '

    我买了一个{{thing}},好{{mood}}啊

    '
    var tempalteStr = `
      {{#students}}
    1. 学生{{name}}的爱好是
        {{#hobbies}}
      1. {{.}}
      2. {{/hobbies}}
    2. {{/students}}
    `
    // 数据 var data = { thing: 'phone', mood: 'happy' } YK_TemplateEngine.render(tempalteStr, data)

    src/index.js

    import parseTemplateToTokens from './parseTemplateToTokens'
    
    // 全局提供YK_TemplateEngine对象
    window.YK_TemplateEngine = {
      // 渲染方法
      render(tempalteStr, data) {
        // 调用parseTemplateToTokens,可以让模板字符串变为tokens数组
        var tokens = parseTemplateToTokens(tempalteStr)
        console.log(tokens)
      },
    };
    

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第20张图片

    4.3.2 完成嵌套数组(重难点)

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第21张图片
    来解决
    遇见#就进栈,遇见/就出栈

    src/nestTokens.js

    折叠tokens,将#xxx和/xxx之间的tokens能够折叠成Array(n),作为xxx的末尾数组 ["#", “xxx”, Array(n)]

    /**
     * 折叠tokens,将#xxx和/xxx之间的tokens能够折叠成Array(n),作为xxx的末尾数组 ["#", "xxx", Array(n)]
     * @param {*} tokens
     */
    export default function nestTokens(tokens) {
      // 结果数组,存储最后的嵌套数组
      var nestedTokens = [];
    
      // 栈,存放 # / 之间的tokens,栈顶的tokens数组中是当前操作的
      // 遇到 # 时,入栈 将#xxx和/xxx之间的tokens能够折叠成Array(n),["#", "xxx", Array(n)]
      // 遇到 / 时,出栈
      var sections = [];
    
      // 收集器数组,为 栈顶 或 结果数组 收集tokens
      // 初始指向 nestedTokens结果数组,引用类型值,所以指向的是同一个数组
      // 入栈后,改变指向:入栈后栈顶末尾数组 token[2]
      // 出栈后,根据栈是否为空改变指向: 出栈后栈顶末尾数组 sections[sections.length - 1][2] 或 结果数组nestedTokens
      var collector = nestedTokens;
    
      for (let token of tokens) {
        // 判断token的第0个元素是什么
        switch (token[0]) {
          case "#":
            // 收集器中放入这个token(初始是nestedTokens数组,当栈中有元素时,指向栈顶token末尾数组)
            collector.push(token);
            // 入栈
            sections.push(token);
            // 改变收集器指向,指向给token添加下标为2的项
            collector = token[2] = [];
            break;
          case "/":
            // 出栈
            sections.pop();
            // 栈不空的情况下 改变收集器为 sections栈顶 末尾数组
            // 栈空就直接指向结果数组
            collector =
              sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
            break;
          // 普通的token
          default:
            // 栈中有元素,就进入栈顶末尾数组;栈中没有元素,就进入结果数组
            collector.push(token);
        }
      }
      return nestedTokens;
    }
    

    4.4 将tokens解析为DOM字符串

    index.html

    
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Documenttitle>
    head>
    
    <body>
      <h1>这是index.htmlh1>
      <div id="container">div>
      <script src="xuni/bundle.js">script>
      <script>
        // 模板字符串
        // var tempalteStr = '

    我买了一个{{thing}},好{{mood}}啊

    '
    var tempalteStr = `
      {{#students}}
    • 学生{{name}}的爱好是
        {{#hobbies}}
      1. {{.}}
      2. {{/hobbies}}
    • {{/students}}
    `
    // 数据 var data = { students: [{ name: '小红', hobbies: ['羽毛球', '跆拳道'] }, { name: '小明', hobbies: ['足球'] }, { name: '小王', hobbies: ['魔术', '学习', '游戏'] } ] } let domStr = YK_TemplateEngine.render(tempalteStr, data) console.log(domStr) let container = document.getElementById('container'); container.innerHTML = domStr;
    script> body> html>

    src/index.js

    import parseTemplateToTokens from "./parseTemplateToTokens";
    import renderTemplate from "./renderTemplate";
    
    // 全局提供YK_TemplateEngine对象
    window.YK_TemplateEngine = {
      // 渲染方法
      render(tempalteStr, data) {
        // 调用parseTemplateToTokens,可以让模板字符串变为tokens数组
        var tokens = parseTemplateToTokens(tempalteStr);
        var domStr = renderTemplate(tokens, data);
        return domStr
      },
    };
    
    

    src/lookup.js 可以在对象中,寻找连续点符号的属性

    /**
     * 可以在dataObj对象中,寻找连续点符号的keyName属性 比如a.b.c  {a:{b:{c:100}}}
     * @param {object} dataObj
     * @param {string} keyName
     */
    export default function lookup(dataObj, keyName) {
      // 判断keyName中有没有点符号,但不能是.本身
      if (keyName.indexOf(".") !== -1 && keyName !== '.') {
        let temp = dataObj; // 临时变量用于周转,一层一层找下去
        let keys = keyName.split(".");
        for (let key of keys) {
          temp = temp[key];
        }
        return temp;
      }
      return dataObj[keyName]
    }
    

    简化版 src/lookup.js

    export default function lookup(dataObj, keyName) {
      // 只有一个元素不影响最终结果,不影响循环语句最终结果,所以不用判断keyName中有没有点符号
      return keyName !== '.' ? keyName.split('.').reduce((prevValue, currentKey) => prevValue[currentKey], dataObj) : dataObj[keyName]
    }
    

    src/renderTemplate.js

    import lookup from './lookup'
    import parseArray from './parseArray'
    /**
     * 让 tokens数组 变成 DOM字符串
     * @param {array} tokens
     * @param {object} data
     */
    export default function renderTemplate(tokens, data) {
      // 结果字符串
      let resultStr = "";
      for (let token of tokens) {
        if (token[0] === "text") {
          resultStr += token[1];
        } else if (token[0] === "name") {
          resultStr += lookup(data, token[1]);
        } else if (token[0] === "#") {
          // 递归调用 renderTemplate
          resultStr += parseArray(token, data)
        }
      }
      return resultStr;
    }
    
    

    src/parseArray.js 递归调用 renderTemplate

    import lookup from "./lookup";
    import renderTemplate from "./renderTemplate";
    /**
     * 处理数组,结合renderTemplate实现递归
     * 参数时token不是tokens 是一个简单的数组 ['#', 'arr', Array[n]]
     * 
     * 递归调用renderTemplate函数,调用次数由data决定
     * 比如data是这样的
     * {
          students: [{
              name: '小红',
              hobbies: ['羽毛球', '跆拳道']
            },
            {
              name: '小明',
              hobbies: ['足球']
            },
            {
              name: '小王',
              hobbies: ['魔术', '学习', '游戏']
            }
          ]
        }
     * parseArray()函数要递归调用renderTemplate函数3次,数组的长度=3  
     */
    export default function parseArray(token, data) {
      // console.log(token, data);
      // 得到data中这个数组需要使用的部分
      let newData = lookup(data, token[1]);
      // console.log(newData);
      // 结果字符串
      let resultStr = '';
      for (let item of newData) {
        resultStr += renderTemplate(token[2], {
          // 展开newData[i] 并加入 点 数组
          ...item,
          '.': item
        })
      }
      return resultStr;
    }
    

    【Vue源码】mustache模板引擎 - 基本使用 - 底层原理 - 手写实现_第22张图片

    4.5 完善空格问题

    1. 普通文字的空格直接去掉
    2. 标签中的空格不能去掉,比如
      <>
      不能去掉class前面的空格
    // 收集开始标记之前的文字
    words = scanner.scanUtil('{{')
    // 存起来
    if (words !== '') {
      // 判断普通文字的空格,还是标签中的空格
      // 标签中的空格不能去掉,比如 
    <>
    不能去掉class前面的空格
    let 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 += words[i] } } } tokens.push(['text', _words]) }

    你可能感兴趣的:(前端框架Vue,vue,javascript)