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

704 阅读9分钟

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

1. 模板引擎的介绍

1.1 模板引擎是什么?

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

数据: 在这里插入图片描述 视图: 在这里插入图片描述 Vue的解决方案

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

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

在这里插入图片描述

1. 使用原生的DOM操作

<!DOCTYPE html>
<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方法

<!DOCTYPE html>
<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 += [
        '<li>',
        '  <div class="hd">' + arr[i].name + '的信息</div>',
        '  <div class="bd">',
        '    <p>姓名:' + arr[i].name + '</p>',
        '    <p>年龄:' + arr[i].age + '</p>',
        '    <p>性别:' + arr[i].sex + '</p>',
        '  </div>',
        '</li>'
      ].join('')
    }
  </script>

</body>

</html>

3. 使用ES6反引号的方法

<!DOCTYPE html>
<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 += `
        <li>
          <div class="hd">${arr[i].name}的基本信息</div>
          <div class="bd">
            <p>姓名:${arr[i].name}</p>
            <p>年龄:${arr[i].age}</p>
            <p>性别:${arr[i].sex}</p>
          </div>
        </li>
      `
    }
  </script>

</body>

</html>

2. mustache基本使用

2.1 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. 最简单的情况——不循环对象数组

在这里插入图片描述

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = `
      <h1>我买了一个{{thing}},好{{mood}}啊</h1>
    `
    var data = {
      thing: '华为手机',
      mood: '开心'
    }
    var domStr = Mustache.render(templateStr, data)
    var container = document.getElementById('container')
    container.innerHTML = domStr
  </script>

2. 循环最简单的数组

在这里插入图片描述

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

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

在这里插入图片描述

  <div id="container"></div>
  <script>
    var templateStr = `
      <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>
    `
    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. 循环嵌套对象数组和简单数组

在这里插入图片描述

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = `
      <ul>
        {{#arr}}
          <li>{{name}}的爱好是:
              <ol>
                {{#hobbies}}
                  <li>{{.}}</li>
                {{/hobbies}}
              </ol>
          </li>
        {{/arr}}  
      </ul>
    `
    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不显示 在这里插入图片描述

  <div id="container"></div>
  <h1></h1>
  <script>
    var templateStr = `
    {{#m}}
      <h1>哈哈哈</h1>
    {{/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的原理

在这里插入图片描述

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

预备知识

replace()方法

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

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

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

在这里插入图片描述

正则的捕获

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

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

var templateStr = '<h1>我买了一个{{thing}},好{{mood}}的啊</h1>'
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 = '<h1>我买了一个{{thing}},花了{{money}},好{{mood}}</h1>'
  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 的实现原理

在这里插入图片描述

3.3 什么是 tokens?

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

1. 最简单的形式

模板字符串

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

tokens

[
  ["text", "<h1>我买了一个"],
  ["name", "thing"],
  ["text", "好"],
  ["name", "mood"],
  ["text", "啊</h1>"]
]

在这里插入图片描述

2. 循环数组情况下的 tokens

在这里插入图片描述

3. 多重循环

在这里插入图片描述

3.4 实现 mustache 库 的重点是

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

4. 手写实现mustache库

4.1 配置webpack环境

使用 webpack 和 webpack-dev-server 构建 在这里插入图片描述 在这里插入图片描述

新建目录 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

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>我是index.html</h1>
  <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 文件的内容 在这里插入图片描述 在这里插入图片描述

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

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <h1>你好!!!</h1>
  <script src="xuni/bundle.js"></script>
  <script>
    var templateStr = '<h1>我买了一个{{thing}},好{{mood}}啊</h1>'
    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("}}");
    }
  },
};

在这里插入图片描述

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 = '<h1>我买了一个{{thing}},好{{mood}}啊</h1>'
var tempalteStr = `
<div>
    <ol>
      {{#students}}
      <li>
        学生{{name}}的爱好是
        <ol>
          {{#hobbies}}
          <li>{{.}}</li>
          {{/hobbies}}
        </ol>
      </li>
      {{/students}}
    </ol>
  </div>`
// 数据
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)
  },
};

在这里插入图片描述

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

在这里插入图片描述来解决 遇见#就进栈,遇见/就出栈

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

<!DOCTYPE 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>Document</title>
</head>

<body>
  <h1>这是index.html</h1>
  <div id="container"></div>
  <script src="xuni/bundle.js"></script>
  <script>
    // 模板字符串
    // var tempalteStr = '<h1>我买了一个{{thing}},好{{mood}}啊</h1>'
    var tempalteStr = `
    <div>
        <ul>
          {{#students}}
          <li class="ii">
            学生{{name}}的爱好是
            <ol>
              {{#hobbies}}
              <li>{{.}}</li>
              {{/hobbies}}
            </ol>
          </li>
          {{/students}}
        </ul>
      </div>`
    // 数据
    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;
}

在这里插入图片描述

4.5 完善空格问题

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