vue源码学习笔记一-Mustache

448 阅读3分钟

mustache

mustache.js 是一个简单强大的 JavaScript 模板引擎,使用它可以简化在 js 代码中的 html 编写。 https://cdn.bootcdn.net/ajax/libs/mustache.js/4.2.0/mustache.js

mustache  语法

  • mustache 语法: 是"胡子"的意思, 因为嵌入标记像胡子 {{}}:
  • mustache 语法, 也被vue 使用。 mustache 语法库是最早的模板引擎库, 比vue 诞生的要早的多。
  • 他的底层是实现机制在当时是非常有创造性的, 轰动性的, 为后续模板引擎的发展提供了崭新的思路。
  • UMD: 就是浏览器中也可以使用, 在 node 环境中也可以使用。

数据 -> 视图

dom
<!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>
      <ul id="list"></ul>
    </div>
  </body>
  <script>
    let arr = [
      { name: "张三", age: 13, sex: "男" },
      { name: "李四", age: 14, sex: "女" },
      { name: "王五", age: 12, sex: "男" },
    ];
    let list = document.getElementById('list');
    for (let i = 0; i < arr.length; i++) {
        let li = document.createElement('li');
        li.innerHTML = arr[i].name + arr[i].age;
        list.append(li)
    }
    // 如果dom层级深,非常笨拙,不好维护
  </script>
</html>

4.png

数组join()方法
<!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>
      <ul id="list">
      </ul>
    </div>
    <script>
      let arr = [
        { name: "张三", age: 13, sex: "男" },
        { name: "李四", age: 14, sex: "女" },
        { name: "王五", age: 12, sex: "男" },
      ];

      let list = document.getElementById("list");
      for (let i = 0; i < arr.length; i++) {
         let str = [
          "<li>",
            "<div>" + arr[i].name + "的信息</div>",
            "<div>",
              "<p>姓名:" + arr[i].name + "</p>",
              "<p>年龄:" + arr[i].age + "</p>",
              "<p>性别:" + arr[i].sex + "</p>",
            "</div>",
          "</li>"
        ].join("");
        list.innerHTML += str
      }
      // 曾经非常流行
    </script>
  </body>
</html>

5.png

ES6模板字符串

<!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>
      <ul id="list"></ul>
    </div>
    <script>
      let arr = [
        { name: "张三", age: 13, sex: "男" },
        { name: "李四", age: 14, sex: "女" },
        { name: "王五", age: 12, sex: "男" },
      ];

      let list = document.getElementById("list");
      for (let i = 0; i < arr.length; i++) {
        list.innerHTML += `
        <li>
            <div>${arr[i].name}的信息</div>
            <div>
                <p>姓名:${arr[i].name}</p>
                <p>年龄:${arr[i].age}</p>
                <p>性别:${arr[i].sex}</p>
            </div>
        </li>
        `;
      }
      
    </script>
  </body>
</html>

Mustache

<!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="./mustache.js"></script>
    <script>
      let templateStr = `
        <ul>
            {{#arr}}
            <li>
                <div>{{name}}的信息</div>
                <div>
                    <p>姓名:{{name}}</p>
                    <p>年龄:{{age}}</p>
                    <p>性别:{{sex}}</p>
                </div>
            </li>
            {{/arr}}
         </ul>
        `;
      let data = {
        arr: [
          { name: "张三", age: 13, sex: "男" },
          { name: "李四", age: 14, sex: "女" },
          { name: "王五", age: 12, sex: "男" },
        ],
      };
      let domStr = mustache.render(templateStr, data);
      let container = document.getElementById('container');
      container.innerHTML = domStr;
    </script>
  </body>
</html>
语法
 <script src="./mustache.js"></script>
 mustache.render(templateStr, data);

templateStr模板字符串

data渲染的数据

数据结构简单-正则
<!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>
     //  正则表达式实现简单模板数据填充 
      let tempStr = "<h1>我买了一个{{phone}},话了{{money}}元,好{{mood}}</h1>";
      let data = {
        phone: " 手机",
        money: 3000,
        mood: "后悔",
      };
      function render(tempStr, data) {
        return tempStr.replace(/\{\{(\w+)\}\}/g, function (findStr, $1) {
          return data[$1];
        });
      }
      let result = render(tempStr, data);
      let container = document.getElementById("container");
      container.innerHTML = result;
    </script>
  </body>
</html>

6.png

如果是复杂的模板数据?

7.png

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>
    <div id="container"></div>
    <script>
      let templateStr = `
        <ul>
            <p>{{title}}</p>
            {{#arr}}
            <li>
                <div>
                    <p>姓名:{{name}}</p>
                    <p>年龄:{{age}}</p>
                    <p>性别:{{sex}}</p>
                    <p>爱好:
                      {{#hobbites}}
                        <span>{{.}}</span>
                      {{/hobbites}}
                    </p>
                </div>
            </li>
            {{/arr}}
         </ul>
        `;
      let data = {
        title: "个人信息",
        arr: [
          {
            name: "张三",
            age: 13,
            sex: "男",
            hobbites: ["篮球", "羽毛球", "乒乓球"],
          },
          {
            name: "李四",
            age: 14,
            sex: "女",
            hobbites: ["足球", "篮球", "乒乓球"],
          },
          {
            name: "王五",
            age: 12,
            sex: "男",
            hobbites: ["游泳", "桌球", "斯诺克"],
          },
        ],
      };
      let result = Bysun_TemplateEngine.render(templateStr, data);
      let container = document.getElementById("container");
      container.innerHTML = result;
    </script>
  </body>
</html>

index.js

import parseTemplateToTokens from './parseTemplateToTokens.js'
import renderTemplate from './renderTemplate.js'
window.Bysun_TemplateEngine = {
    // 渲染方法
    render(templateStr, data) {
        // 实例化一个扫描器,构造时候提供一个参数,这个参数就是模板字符串
        // 也就是说这个扫描器就是处理模板字符串的
        let tokens = parseTemplateToTokens(templateStr);
        // console.log('tokens',tokens)
        let domStr = renderTemplate(tokens,data);
        // console.log(domStr,'domStr')
        return domStr;
    }
}

Scanner.js

// 扫描器
export default class Scanner {
    constructor(templateStr) {
        // 指针
        this.pos = 0;
        // 尾巴,初始就是模板字符串
        this.tail = templateStr;
        this.templateStr = templateStr;
    }
    // 就是走过指定内容,没有返回值
    scan(tag) {
     if(this.tail.indexOf(tag) == 0) {
        // tag  {{  长度是2,就让指针后移多少位
        this.pos += tag.length; 
        // 改变尾巴,当前指针开始,到结束的全部字符
        this.tail = this.templateStr.substr(this.pos)
     }
    }
    // this.templateStr = "我买了一个{{phone}},好{{mood}}";
    // 让指针扫描,直到遇见指定内容结束,并且能够返回结束之前路过的文字
    scanUnit(stopTag) {
        const pos_backup = this.pos; // 0
        // 当尾巴的开头不是stopTag的时候,就说明还没有扫描到stopTag
        // && 防止找不到,寻找到最后停止下来
        while (this.tail.indexOf(stopTag) != 0 && !this.eos()) {
            this.pos++
            // 改变尾巴,当前指针开始,到结束的全部字符
            this.tail = this.templateStr.substring(this.pos)
        }
        return this.templateStr.substring(pos_backup, this.pos)
    }
    // 指针是否到头
    eos() {
        return this.pos >= this.templateStr.length
    }
}

parseTemplateToTokens.js

import Scanner from './Scanner.js';
import nestToken from './nestTokens.js'
export default function parseTemplateToTokens(templateStr) {
    let tokens = [];
    // 创建扫描器    
    let scanner = new Scanner(templateStr);
    let words;
    while (!scanner.eos()) {
        //收集开始标记{{之前的文字
        words = scanner.scanUnit('{{');
        // 存入数组
        if (words != '') {
            let _word = "";
            let flag = false;
            for (let i = 0; i < words.length; i++) {
                const temp = words[i];
                if (temp === "<") {
                    flag = true
                } else if (temp === ">") {
                    flag = false
                }
                // 标签中的样式空格要保留  <p class="bd"></p>
                if (/\s/.test(temp)) {
                    if (flag) {
                        _word += temp
                    }
                } else {
                    _word += temp
                }
            }
            tokens.push(['text', _word])
        }
        // 过{{
        scanner.scan('{{')

        //收集开始标记}}之前的文字
        words = scanner.scanUnit('}}');
        // 存入数组
        if (words != '') {
            // 当模板字符串层级很深,数据结构有很多子项,且子项也是数组
            if (words[0] == '#') {
                tokens.push(['#', words.substr(1)])
            } else if (words[0] == '/') {
                tokens.push(['/', words.substr(1)])
            } else {
                tokens.push(['name', words]);
            }
        }
        // 过{{
        scanner.scan('}}')
    }
    // console.log('---', tokens)
    return nestToken(tokens);
}

8.png nestTokens.js

export default function nestToken(tokens){
    let nestTokens = [];  // 返回数据
    let sections = [];
    // 收集器,收集子孙元素,指向nestTokens数组,引用类型值指向的是同一个数组
    // 遇见#,收集器会遇到当前token的下标为2的新数组
    let collector = nestTokens;
    // 栈结构,存放小tokens, 栈顶(靠近端口的,最新进入的)tokens数组中前操作的这个tokens小数组
    tokens.forEach((token,index) => {
      switch (token[0]) {
        case '#':
          collector.push(token); // 收集器放入这个token
          sections.push(token); // 入栈
          collector = token[2]= []; // 给token 添加下标为2的项目,并让收集器指向它
          break
        case '/':
          sections.pop(); // 出栈 pop会返回刚刚弹出的项
          // 改变收集器为栈结构队尾(队尾就是栈顶) 那项下标为2的数组
          collector = sections.length > 0 ? sections[sections.length-1][2] : nestTokens
          break
        default:
          collector.push(token)
          break
      }
    })
    return nestTokens
  }

renderTemplate.js

// tokens转为字符串
export default function renderTemplate(tokens, data) {
    let res = '';
    for (let i = 0; i < tokens.length; i++) {
        let token = tokens[i];
        if (token[0] == 'text') {
            res += token[1];
        } else if (token[0] == 'name') {
            // 如果是name,需要处理 a.b.c 
            res += handle(data, token[1])
        } else if (token[0] == '#') {
            // 数据类型  ['#','aaaa',['xxxx']]  需要调用renderTemplate方法处理
            // 数组要解析处理,需要循环然后调用renderTemplate方法
            res += handleArray(token, data)
        }
    }
    return res;
}
// 处理 数组中 name 为 a.b.c 的变量
function handle(dataObj, keyName) {
    /*
    let a = {
        b: {
            c: 1,
        }
    }
    dataObj = { a }
    keyName = 'a.b.c'
    */
    if (keyName.indexOf('.') !== -1 && keyName !== ".") {
        let temp = dataObj
        let keys = keyName.split('.') // ['a','b','c']
        for (let i = 0; i < keys.length; i++) {
            // console.log(keys[i]) 
            // console.log(temp[keys[i]])
            /*
                1 keys[i] = a; temp[keys[i]] = {b:{c:1}};
                2 keys[i] = b; temp[keys[i]] = {c:1};
                3 keys[i] = c; temp[keys[i]] = 1;
            */
            temp = temp[keys[i]]
        }
        return temp
    }
    // 没有.,则返回
    return dataObj[keyName];
}

function handleArray(token, data) {
    let res = ''
    // 数据类型 token =  ['#','aaaa',['xxxx']]  需要调用renderTemplate方法处理
    let list = handle(data, token[1]);
    if (list && list.length) {
        for (let i = 0; i < list.length; i++) {
            res += renderTemplate(token[2], {
                ...list[i],
                '.': list[i]
            })
        }
    }
    return res
}

9.png

10.png

资源来源

B站视频地址