Skip to content

前言

通常开发的时候会一个遍历数据的操作,在 vue 中时通过 v-for 来遍历数据的。今天就来聊聊这个 Vue 中的 mustache 模板引擎。

在没有 mustache 之前,要想数据变成视图有什么方法,欲知答案就看下面

纯 DOM 法

操作 DOM 的属性 进行 createElement(创建)、appendChild(插入),代码如下:

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>数组join法</title>
  </head>

  <body>
    <ul id="list"></ul>
    <script type="text/javascript">
      var arr = [
        {
          name: "周星星",
          age: 19,
          sex: "男",
        },
        {
          name: "朱茵",
          age: 20,
          sex: "女",
        },
        {
          name: "李嘉欣",
          age: 21,
          sex: "女",
        },
      ];
      const list = document.getElementById("list");

      for (let i of arr) {
        list.innerHTML += [
          "<li>",
          '<div class = "hd" > ' + i.name + "的基本信息 </div>",
          '<div class="bd">',
          "<p>姓名:" + i.name + "</p> ",
          "<p> 年龄:" + i.age + "</p>",
          "<p>性别:" + i.sex + "</p>",
          "</div>",
          "</li >",
        ].join("");
      }
    </script>
  </body>
</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>数组join法</title>
  </head>

  <body>
    <ul id="list"></ul>
    <script type="text/javascript">
      var arr = [
        {
          name: "周星星",
          age: 19,
          sex: "男",
        },
        {
          name: "朱茵",
          age: 20,
          sex: "女",
        },
        {
          name: "李嘉欣",
          age: 21,
          sex: "女",
        },
      ];
      const list = document.getElementById("list");

      for (let i of arr) {
        list.innerHTML += [
          "<li>",
          '<div class = "hd" > ' + i.name + "的基本信息 </div>",
          '<div class="bd">',
          "<p>姓名:" + i.name + "</p> ",
          "<p> 年龄:" + i.age + "</p>",
          "<p>性别:" + i.sex + "</p>",
          "</div>",
          "</li >",
        ].join("");
      }
    </script>
  </body>
</html>

效果图:

image.png

ES6 模板字符串(反引号)法

ES6 的一个语法糖,在平常的开发中也会用到不少,代码如下:

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>ES6 模板字符串(反引号)法</title>
  </head>

  <body>
    <ul id="list"></ul>
    <script type="text/javascript">
      var arr = [
        {
          name: "周星星",
          age: 19,
          sex: "男",
        },
        {
          name: "朱茵",
          age: 20,
          sex: "女",
        },
        {
          name: "李嘉欣",
          age: 21,
          sex: "女",
        },
      ];
      const list = document.getElementById("list");

      for (let i of arr) {
        list.innerHTML += `<li><div class="hd"> ${i.name}的基本信息 </div><div class="bd"><p>姓名:${i.name}</p> <p> 年龄:${i.age}</p><p>性别:${i.sex}</p></div></li>`;
      }
    </script>
  </body>
</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>ES6 模板字符串(反引号)法</title>
  </head>

  <body>
    <ul id="list"></ul>
    <script type="text/javascript">
      var arr = [
        {
          name: "周星星",
          age: 19,
          sex: "男",
        },
        {
          name: "朱茵",
          age: 20,
          sex: "女",
        },
        {
          name: "李嘉欣",
          age: 21,
          sex: "女",
        },
      ];
      const list = document.getElementById("list");

      for (let i of arr) {
        list.innerHTML += `<li><div class="hd"> ${i.name}的基本信息 </div><div class="bd"><p>姓名:${i.name}</p> <p> 年龄:${i.age}</p><p>性别:${i.sex}</p></div></li>`;
      }
    </script>
  </body>
</html>

效果也是跟上面是一样的。

mustache 的基本使用

mustache 简介

  • 一款 javascript 前端模板引擎,官方文档地址:github
  • mustache 是 “胡子” 的意思,因为它的嵌入标记{ { } }非常像胡子
  • { { } }的语法也被 Vue 沿用
  • mustache 是最早的模板引擎库,比 Vue 诞生的还早了

基本语法

  1. { { keyName } }: 读取属性值, 如果有 html 标签的话,会被转义。
  2. { { { keyName } } }: 读取属性值且原样输出,即 html 不进行转义。
  3. { { #keyName } } { { /keyName } }: 用于遍历。
  4. { { ^keyName } } { { /keyName } }: 反义数据,当 keyName 不存在、或为 null,或为 false 时会生效。可以理解相当于我们 js 中的 !(非)。
  5. { {.} }: 用于遍历数组。
  6. { { !comments } }: 用于注释。
  7. Partials: 使用可重用的模板,使用方式:{ {> 变量} }

我们这里并没有全部用上

循环对象数组

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>
    <div id="container"></div>
    <script type="text/template" id="mytemplate">
        <ul>
            {{#arr}}
                <li>
                    <div class="hd">{{name}}的基本信息</div>
                    <div class="bd">
                        <p>姓名:{{name}}</p>
                        <p>性别:{{sex}}</p>
                        <p>年龄:{{age}}</p>
                    </div>
                </li>
            {{/arr}}
        </ul>
    </script>

    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        var templateStr = document.getElementById('mytemplate').innerHTML

        var data = {
            arr: [{
                    name: "周星星",
                    age: 19,
                    sex: "男"
                },
                {
                    name: "朱茵",
                    age: 20,
                    sex: "女"
                },
                {
                    name: "李嘉欣",
                    age: 21,
                    sex: "女"
                }
            ]
        };

        var domStr = Mustache.render(templateStr,data)
        var container = document.getElementById("container")
        container.innerHTML = domStr
    </script>
</body>

</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>
    <div id="container"></div>
    <script type="text/template" id="mytemplate">
        <ul>
            {{#arr}}
                <li>
                    <div class="hd">{{name}}的基本信息</div>
                    <div class="bd">
                        <p>姓名:{{name}}</p>
                        <p>性别:{{sex}}</p>
                        <p>年龄:{{age}}</p>
                    </div>
                </li>
            {{/arr}}
        </ul>
    </script>

    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        var templateStr = document.getElementById('mytemplate').innerHTML

        var data = {
            arr: [{
                    name: "周星星",
                    age: 19,
                    sex: "男"
                },
                {
                    name: "朱茵",
                    age: 20,
                    sex: "女"
                },
                {
                    name: "李嘉欣",
                    age: 21,
                    sex: "女"
                }
            ]
        };

        var domStr = Mustache.render(templateStr,data)
        var container = document.getElementById("container")
        container.innerHTML = domStr
    </script>
</body>

</html>

不循环

js
<!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>
    <div id="container"></div>
    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        var templateStr = `<h1>周星星的电影{{movie}},好{{mood}}`

        var data = {
            movie: "逃学威龙",
            mood: "搞笑"
        }

        var domStr = Mustache.render(templateStr,data)

        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>
</body>

</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>
    <div id="container"></div>
    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        var templateStr = `<h1>周星星的电影{{movie}},好{{mood}}`

        var data = {
            movie: "逃学威龙",
            mood: "搞笑"
        }

        var domStr = Mustache.render(templateStr,data)

        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>
</body>

</html>

循环简单数组

js
<!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>
    <div id="container"></div>
    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        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>
</body>

</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>
    <div id="container"></div>
    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        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>
</body>

</html>

数组的嵌套

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>
    <div id="container"></div>
    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        var templateStr = `
        <div class="aa">
            <ul>
                {{#arr}}
                    <li class="myli">
                        {{name}}的电影有:
                        <ol>
                            {{#movie}}
                            <li>{{.}}</li>
                            {{/movie}}
                        </ol>
                    </li>
                {{/arr}}
            </ul>
        </div>
        `

        var data = {
            arr:[
            {
                    name: "周星星",
                    age: 19,
                    sex: "男",
                    movie:["逃学威龙","赌圣","大圣娶亲"]
                },
                {
                    name: "朱茵",
                    age: 20,
                    sex: "女",
                    movie:["大话西游之大圣娶亲"]
                },
                {
                    name: "李嘉欣",
                    age: 21,
                    sex: "女",
                    movie:["笑傲江湖2:东方不败"]
                }
            ]
        }

        var domStr = Mustache.render(templateStr,data)

        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>
</body>

</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>
    <div id="container"></div>
    <script src="./js/mustache.js"></script>
    <script type="text/javascript">
        var templateStr = `
        <div class="aa">
            <ul>
                {{#arr}}
                    <li class="myli">
                        {{name}}的电影有:
                        <ol>
                            {{#movie}}
                            <li>{{.}}</li>
                            {{/movie}}
                        </ol>
                    </li>
                {{/arr}}
            </ul>
        </div>
        `

        var data = {
            arr:[
            {
                    name: "周星星",
                    age: 19,
                    sex: "男",
                    movie:["逃学威龙","赌圣","大圣娶亲"]
                },
                {
                    name: "朱茵",
                    age: 20,
                    sex: "女",
                    movie:["大话西游之大圣娶亲"]
                },
                {
                    name: "李嘉欣",
                    age: 21,
                    sex: "女",
                    movie:["笑傲江湖2:东方不败"]
                }
            ]
        }

        var domStr = Mustache.render(templateStr,data)

        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>
</body>

</html>

布尔值

js
<!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>
    <div id="container"></div>

    <script src="./js/mustache.js"></script>
    <script>
        var templateStr = `
            {{#m}}
                <h1>你好</h1>
            {{/m}}
        `;

        var data = {
            m: false
        };

        var domStr = Mustache.render(templateStr, data);

        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>
</body>

</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>
    <div id="container"></div>

    <script src="./js/mustache.js"></script>
    <script>
        var templateStr = `
            {{#m}}
                <h1>你好</h1>
            {{/m}}
        `;

        var data = {
            m: false
        };

        var domStr = Mustache.render(templateStr, data);

        var container = document.getElementById('container');
        container.innerHTML = domStr;
    </script>
</body>

</html>

正则表达式实现简单模板数据填充

js
<!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>
    <script>
        var templateStr = '<h1>我买了一个{{thing}},花了{{money}}元,好{{mood}}</h1>';

        var data = {
            thing: '白菜',
            money: 5,
            mood: '激动'
        };

        // 最简单的模板引擎的实现机理,利用的是正则表达式中的replace()方法。
        // replace()的第二个参数可以是一个函数,这个函数提供捕获的东西的参数,就是$1
        // 结合data对象,即可进行智能的替换
        function render(templateStr, data) {
            return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
                return data[$1];
            });
        }

        var result = render(templateStr, data);
        console.log(result);
    </script>
</body>

</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>
    <script>
        var templateStr = '<h1>我买了一个{{thing}},花了{{money}}元,好{{mood}}</h1>';

        var data = {
            thing: '白菜',
            money: 5,
            mood: '激动'
        };

        // 最简单的模板引擎的实现机理,利用的是正则表达式中的replace()方法。
        // replace()的第二个参数可以是一个函数,这个函数提供捕获的东西的参数,就是$1
        // 结合data对象,即可进行智能的替换
        function render(templateStr, data) {
            return templateStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
                return data[$1];
            });
        }

        var result = render(templateStr, data);
        console.log(result);
    </script>
</body>

</html>

mustache基本原理

image.png

由上图可以得知,mustache首先是将模板字符串编译为tokens,然后用tokens将数据结合,解析成dom字符串,最后渲染上树的。

先来看一下tokens的样子

先将mustache源码中的代码改一下,输出一下tokens长什么样

image.png

再来回顾刚才的例子来看一下,每个例子输出的tokens是什么样的

1、不循环,简单字符串

image.png

2、有循环时

image.png

当模板字符串中有循环存在时,它将被编译为嵌套更深的tokens

3、双重循环时

image.png

当循环是双重的,那么tokens会更深一层

总结:

1、遇到{ {,会将前面的字符串作为下标0是text的一个数组输出

2、遇到'#',会将{ {#students} }{ {/students} }之间的存到数组中的下标2中,输出为:['#','student',[中间所有字符串的tokens组合]]

3、如果{}中间是单纯的name时,就会下标0是name的一个数组输出

4、{ {} }并没有出现在tokens数组中

手动实现迷你版的mustache

本次创建项目用webpack来创建,搭建一个小的服务即可,目的只是想在更新的时候,能起到热更新的效果

js
npm init
npm init

首先通过上面初始化一个项目

js
npm i webpack@4.44.2 webpack-cli@3.3.12 webpack-dev-server@3.11.0
npm i webpack@4.44.2 webpack-cli@3.3.12 webpack-dev-server@3.11.0

再下载webpack4所需要的配置项 最后package.json的配置为

image.png

再来配置一下webpack.config.js

js
const path = require('path')
module.exports = {
    mode: "development",
    entry: "./src/index.js",
    output: {
        filename: "bundle.js"
    },
    devServer: {
        contentBase: path.join(__dirname, "www"),
        compress: false,
        port: 8080,
        publicPath: "/zxx/"
    }
}
const path = require('path')
module.exports = {
    mode: "development",
    entry: "./src/index.js",
    output: {
        filename: "bundle.js"
    },
    devServer: {
        contentBase: path.join(__dirname, "www"),
        compress: false,
        port: 8080,
        publicPath: "/zxx/"
    }
}

入口src/index.js

js
import parseTemplateToTokens from "./parseTemplateToTokens"
import renderTemplate from "./renderTemplate"

window.templateEngine = {
    render(templateStr, data) {
        // console.log(templateStr,data)
        // console.log(templateStr)
        var tokens = parseTemplateToTokens(templateStr)
        var domStr = renderTemplate(tokens, data)
        return domStr;
    }
}
import parseTemplateToTokens from "./parseTemplateToTokens"
import renderTemplate from "./renderTemplate"

window.templateEngine = {
    render(templateStr, data) {
        // console.log(templateStr,data)
        // console.log(templateStr)
        var tokens = parseTemplateToTokens(templateStr)
        var domStr = renderTemplate(tokens, data)
        return domStr;
    }
}

parseTemplateToTokens 拆分字符串,放到tokens

js
import Scanner from './Scanner.js';
import nestTokens from './nestTokens.js';

/* 
    将模板字符串变为tokens数组
*/
export default function parseTemplateToTokens(templateStr) {
    var tokens = [];
    // 创建扫描器
    var scanner = new Scanner(templateStr);
    var words;
    // 让扫描器工作
    while (!scanner.eos()) {
        // 收集开始标记出现之前的文字
        words = scanner.scanUtil('{{');
        if (words != '') {
            // 尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格
            // 标签中的空格不能去掉,比如<div class="box">不能去掉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 += ' ';
                    }
                }
            }
            // 存起来,去掉空格
            tokens.push(['text', _words]);
        }
        // 过双大括号
        scanner.scan('{{');
        // 收集开始标记出现之前的文字
        words = scanner.scanUtil('}}');
        if (words != '') {
            // 这个words就是{{}}中间的东西。判断一下首字符
            if (words[0] == '#') {
                // 存起来,从下标为1的项开始存,因为下标为0的项是#
                tokens.push(['#', words.substring(1)]);
            } else if (words[0] == '/') {
                // 存起来,从下标为1的项开始存,因为下标为0的项是/
                tokens.push(['/', words.substring(1)]);
            } else {
                // 存起来
                tokens.push(['name', words]);
            }
        }
        // 过双大括号
        scanner.scan('}}');
    }

    // 返回折叠收集的tokens
    return nestTokens(tokens);
}
import Scanner from './Scanner.js';
import nestTokens from './nestTokens.js';

/* 
    将模板字符串变为tokens数组
*/
export default function parseTemplateToTokens(templateStr) {
    var tokens = [];
    // 创建扫描器
    var scanner = new Scanner(templateStr);
    var words;
    // 让扫描器工作
    while (!scanner.eos()) {
        // 收集开始标记出现之前的文字
        words = scanner.scanUtil('{{');
        if (words != '') {
            // 尝试写一下去掉空格,智能判断是普通文字的空格,还是标签中的空格
            // 标签中的空格不能去掉,比如<div class="box">不能去掉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 += ' ';
                    }
                }
            }
            // 存起来,去掉空格
            tokens.push(['text', _words]);
        }
        // 过双大括号
        scanner.scan('{{');
        // 收集开始标记出现之前的文字
        words = scanner.scanUtil('}}');
        if (words != '') {
            // 这个words就是{{}}中间的东西。判断一下首字符
            if (words[0] == '#') {
                // 存起来,从下标为1的项开始存,因为下标为0的项是#
                tokens.push(['#', words.substring(1)]);
            } else if (words[0] == '/') {
                // 存起来,从下标为1的项开始存,因为下标为0的项是/
                tokens.push(['/', words.substring(1)]);
            } else {
                // 存起来
                tokens.push(['name', words]);
            }
        }
        // 过双大括号
        scanner.scan('}}');
    }

    // 返回折叠收集的tokens
    return nestTokens(tokens);
}

Scanner 扫描器

js
/* 
    扫描器类
*/
export default class Scanner {
    constructor(templateStr) {
        // 将模板字符串写到实例身上
        this.templateStr = templateStr;
        // 指针
        this.pos = 0;
        // 尾巴,一开始就是模板字符串原文
        this.tail = templateStr;
    }

    // 功能弱,就是走过指定内容,没有返回值
    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            // tag有多长,比如{{长度是2,就让指针后移多少位
            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);
        }

        return this.templateStr.substring(pos_backup, this.pos);
    }

    // 指针是否已经到头,返回布尔值。end of string
    eos() {
        return this.pos >= this.templateStr.length;
    }
};
/* 
    扫描器类
*/
export default class Scanner {
    constructor(templateStr) {
        // 将模板字符串写到实例身上
        this.templateStr = templateStr;
        // 指针
        this.pos = 0;
        // 尾巴,一开始就是模板字符串原文
        this.tail = templateStr;
    }

    // 功能弱,就是走过指定内容,没有返回值
    scan(tag) {
        if (this.tail.indexOf(tag) == 0) {
            // tag有多长,比如{{长度是2,就让指针后移多少位
            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);
        }

        return this.templateStr.substring(pos_backup, this.pos);
    }

    // 指针是否已经到头,返回布尔值。end of string
    eos() {
        return this.pos >= this.templateStr.length;
    }
};

nestTokens 将tokens折叠

js
/* 
    函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
    // 结果数组
    var nestedTokens = [];
    // 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
    var sections = [];
    // 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
    // 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
    var collector = nestedTokens;

    console.log(tokens)

    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] : nestedTokens;
                break;
            default:
                // 甭管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为2的数组,甭管是谁,推入collctor即可。
                collector.push(token);
        }
        // console.log(collector,nestedTokens)
        //         debugger
    }

    return nestedTokens;
};
/* 
    函数的功能是折叠tokens,将#和/之间的tokens能够整合起来,作为它的下标为3的项
*/
export default function nestTokens(tokens) {
    // 结果数组
    var nestedTokens = [];
    // 栈结构,存放小tokens,栈顶(靠近端口的,最新进入的)的tokens数组中当前操作的这个tokens小数组
    var sections = [];
    // 收集器,天生指向nestedTokens结果数组,引用类型值,所以指向的是同一个数组
    // 收集器的指向会变化,当遇见#的时候,收集器会指向这个token的下标为2的新数组
    var collector = nestedTokens;

    console.log(tokens)

    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] : nestedTokens;
                break;
            default:
                // 甭管当前的collector是谁,可能是结果nestedTokens,也可能是某个token的下标为2的数组,甭管是谁,推入collctor即可。
                collector.push(token);
        }
        // console.log(collector,nestedTokens)
        //         debugger
    }

    return nestedTokens;
};

renderTemplate 让tokens数组变为dom字符串

js
import lookup from './lookup.js';
import parseArray from './parseArray.js';
/* 
    函数的功能是让tokens数组变为dom字符串
*/
export default function renderTemplate(tokens, data) {
    // 结果字符串
    var resultStr = '';
    // 遍历tokens
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        // 看类型
        if (token[0] == 'text') {
            // 拼起来
            resultStr += token[1];
        } else if (token[0] == 'name') {
            // 如果是name类型,那么就直接使用它的值,当然要用lookup
            // 因为防止这里是“a.b.c”有逗号的形式
            resultStr += lookup(data, token[1]);
        } else if (token[0] == '#') {
            resultStr += parseArray(token, data);
        }
    }

    return resultStr;
}
import lookup from './lookup.js';
import parseArray from './parseArray.js';
/* 
    函数的功能是让tokens数组变为dom字符串
*/
export default function renderTemplate(tokens, data) {
    // 结果字符串
    var resultStr = '';
    // 遍历tokens
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        // 看类型
        if (token[0] == 'text') {
            // 拼起来
            resultStr += token[1];
        } else if (token[0] == 'name') {
            // 如果是name类型,那么就直接使用它的值,当然要用lookup
            // 因为防止这里是“a.b.c”有逗号的形式
            resultStr += lookup(data, token[1]);
        } else if (token[0] == '#') {
            resultStr += parseArray(token, data);
        }
    }

    return resultStr;
}

lookup 通过"."在对象中选择相应的值

js
/* 
    功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
    比如,dataObj是
    {
        a: {
            b: {
                c: 100
            }
        }
    }
    那么lookup(dataObj, 'a.b.c')结果就是100
    不忽悠大家,这个函数是某个大厂的面试题
*/
export default function lookup(dataObj, keyName) {
    // 看看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];
};
/* 
    功能是可以在dataObj对象中,寻找用连续点符号的keyName属性
    比如,dataObj是
    {
        a: {
            b: {
                c: 100
            }
        }
    }
    那么lookup(dataObj, 'a.b.c')结果就是100
    不忽悠大家,这个函数是某个大厂的面试题
*/
export default function lookup(dataObj, keyName) {
    // 看看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 处理数组,结合renderTemplate实现递归

js
import lookup from './lookup.js';
import renderTemplate from './renderTemplate.js';

/* 
    处理数组,结合renderTemplate实现递归
    注意,这个函数收的参数是token!而不是tokens!
    token是什么,就是一个简单的['#', 'students', [

    ]]
    
    这个函数要递归调用renderTemplate函数,调用多少次???
    千万别蒙圈!调用的次数由data决定
    比如data的形式是这样的:
    {
        students: [
            { 'name': '小明', 'hobbies': ['游泳', '健身'] },
            { 'name': '小红', 'hobbies': ['足球', '蓝球', '羽毛球'] },
            { 'name': '小强', 'hobbies': ['吃饭', '睡觉'] },
        ]
    };
    那么parseArray()函数就要递归调用renderTemplate函数3次,因为数组长度是3
*/

export default function parseArray(token, data) {
    // 得到整体数据data中这个数组要使用的部分
    var v = lookup(data, token[1]);
    // 结果字符串
    var resultStr = '';
    // 遍历v数组,v一定是数组
    // 注意,下面这个循环可能是整个包中最难思考的一个循环
    // 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
    switch (typeof v) {
        case 'object':
            for (let i of v) {
                // console.log(i)
                resultStr += renderTemplate(token[2], {
                    ...i,
                    '.': i
                })
            }
            break;
        case 'boolean':
            if(v){
                resultStr += renderTemplate(token[2])
            }else{
                resultStr += ''
            }
            break;
        default:
            throw new Error("传入了位置类型,请传入对象类型或者布尔类型")
            break;
    }
    return resultStr;
};
import lookup from './lookup.js';
import renderTemplate from './renderTemplate.js';

/* 
    处理数组,结合renderTemplate实现递归
    注意,这个函数收的参数是token!而不是tokens!
    token是什么,就是一个简单的['#', 'students', [

    ]]
    
    这个函数要递归调用renderTemplate函数,调用多少次???
    千万别蒙圈!调用的次数由data决定
    比如data的形式是这样的:
    {
        students: [
            { 'name': '小明', 'hobbies': ['游泳', '健身'] },
            { 'name': '小红', 'hobbies': ['足球', '蓝球', '羽毛球'] },
            { 'name': '小强', 'hobbies': ['吃饭', '睡觉'] },
        ]
    };
    那么parseArray()函数就要递归调用renderTemplate函数3次,因为数组长度是3
*/

export default function parseArray(token, data) {
    // 得到整体数据data中这个数组要使用的部分
    var v = lookup(data, token[1]);
    // 结果字符串
    var resultStr = '';
    // 遍历v数组,v一定是数组
    // 注意,下面这个循环可能是整个包中最难思考的一个循环
    // 它是遍历数据,而不是遍历tokens。数组中的数据有几条,就要遍历几条。
    switch (typeof v) {
        case 'object':
            for (let i of v) {
                // console.log(i)
                resultStr += renderTemplate(token[2], {
                    ...i,
                    '.': i
                })
            }
            break;
        case 'boolean':
            if(v){
                resultStr += renderTemplate(token[2])
            }else{
                resultStr += ''
            }
            break;
        default:
            throw new Error("传入了位置类型,请传入对象类型或者布尔类型")
            break;
    }
    return resultStr;
};

总结

自己手写一下,说不定会有不一样的收获呢。