VUE源码:模板引擎mustache

模板引擎的定义

模板引擎就是将数据变为视图最优雅的解决方案
例如:VUE的v-for、mustache
历史上数据变为视图方法:

  • 纯DOM方法
  • 数组join
  • ES6的反引号法:``${a} `
  • 模板引擎

mustache的基本使用

官方git:https://github.com/janl/mustache.js
例子:

let arr = [
  { name: "小红", sex: "女", age: 18 },
  { name: "小黑", sex: "男", age: 19 },
  { name: "小白", sex: "女", age: 17 },
];

1.引入mustache库
2.语法

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

Mustache.render(templateStr,data)

手写原理代码(简化版)

没有包含布尔值,其它基本实现
index:

import parseTempalteToTokens from "./parseTempalteToTOkens.js";
import renderTamplate from "./renderTamplate.js";
Window.templateEngine = {
  render(templateStr, data) {
    let tokens = parseTempalteToTokens(templateStr);
    // console.log(tokens);
    // 调用renderTemplate函数,让tokens数组变为dom字符串
    let domStr = renderTamplate(tokens, data);
    return domStr
  },
};

parseTempalteToTokens:

import Scanner from "./Scanner";
import nestTokens from "./nestTokens";
/**
 *
 * 将模板字符串变为token数组
 *
 */
export default function parseTempalteToTokens(templateStr) {
  let tokens = [];
  // 创建扫描器
  let scanner = new Scanner(twmplateStr);
  let words;
  // scanner没有到头
  while (!scanner.eos()) {
    // 收集开始标记之前的文字
    words = scanner.scanUtil("{{");
    // 存储
    if (words != " ") {
      // 普通文字中的空格没有意义;标签中的空格保留<div class="box"></div>
      let isInJJH = false;
      let _words = "";
      for (let i = 0; i < words.length; i++) {
        if (words[i] == "<") {
          isInJJH = true;
        } else {
          isInJJH = false;
        }
        // 如果不是空格,拼接
        if (!/\s/.test(words[i])) {
          _words += words[i];
        } else {
          // 如果是空格且在标签内,拼接
          if (isInJJH) {
            _words += " ";
          }
        }
      }
      tokens.push(["text", words]);
    }
    // 跳过双大括号
    scanner.scan("{{");
    // 收集}}之前的文字
    words = scanner.scanUtil("}}");
    // 存储,此时的words为{{}}中的内容
    if (words != "") {
      if (words[0] == "#") {
        tokens.push(["#", words.substring(1)]);
      } else if (words[0] == "/") {
        tokens.push(["/", words.substring(1)]);
      } else {
        tokens.push(["name", words.substring(1)]);
      }
    }
    // 跳过双大括号
    scanner.scan("}}");
  }
  return nestTokens(tokens);
}

Scanner:

/**
 * 扫描器类
 */
export default class Scannner {
  constructor(templateStr) {
    this.templateStr = templateStr;
    console.log(templateStr);
    // 指针
    this.pos = 0;
    // 尾巴指针
    this.tail = templateStr;
  }
  // 走过指定内容
  scan(tag) {
    if (this.tail.indexOf(tag) == 0) {
      // tag有多长,就让pos后移多少位
      this.pos += tag.length;
      // 改变尾巴为从当前到最后
      this.tail = this.templateStr.substring(this.pos);
    }
  }
  // 让指针进行扫描,直到遇到指定内容,并返回结束之前的文字
  scanUtil(stopTag) {
    // 记录执行方法的pos的值
    let pos_backyp = this.pos;
    // 寻找标记且没到头
    while (this.tail.indexOf(stopTag) && !this.eos()) {
      this.pos++;
      // 改变尾巴为从当前到最后
      this.tail = this.templateStr.substr(this.pos);
    }
    return this.templateStr.substring(pos_backyp, this.pos);
  }
  // 指针是否到头,返回布尔值
  eos() {
    return this.pos >= this.templateStr.length;
  }
}

nestTokens:

/**
 * 折叠,将#和/ 之间的tokens整合起来作为它下标为3的向
 * 将零散的tokens串起来--->栈:遇见#入栈;遇见/出栈
 */
export default function nestTokens(tokens) {
  // 结果数组
  let nestedTokens = [];
  // 收集器,注意收集器会变化
  let collector = nestedTokens;
  // 栈结构
  let sections = [];
  for (let i = 0; i < tokens.length; i++) {
    let token = tokens[i];
    switch (token[0]) {
      case "#":
        // 收集器存储token
        collector.push(token);
        // 入栈
        sections.push(token);
        // 收集器要换人
        collector = token[2] = [];
        break;
      case "/":
        // 出战
        sections.pop();
        // 改变收集器为上一层
        collector =
          sections.length > 0 ? sections[sections.length - 1][2] : nestedTokens;
        break;
      default:
        collector.push(token);
    }
  }
  return nestedTokens;
}

renderTamplate:

import lookup from "./lookup.js";
import parseArray from "./parseArray.js";
/**
 * 让tokens数组变为dom字符串
 * 递归:#标记的tokens需要处理它下标为1
 */
export default function renderTamplate(tokens, data) {
  // 结果字符串
  let resultStr = "";
  // 遍历tokens
  for (let i = 0, len = tokens.length; i < len; i++) {
    let token = tokens[i];
    // 看类型
    if (token[0] == "text") {
      resultStr += token[1];
    } else if (token[0] == "name") {
      // 如果试name类型,直接使用它的值,要调用lookup
      // 防止是a.d.c
      resultStr += lookup(data, token[1]);
    } else if (token[0] == "#") {
      resultStr += parseArray(token, data);
    }
  }
  return resultStr;
}

parseArray:

import lookup from "./lookup.js";
import renderTemplate from "./renderTamplate.js";
/**
 * 处理token数组,结合renderTemplate实现递归
 * 注意:接收参数为token
 * ['#',student,[]]
 *
 *
 * 这个函数:递归调用renderTemplate,次数有data决定
 *
 */
export default function parseArray(token, data) {
  // 得到data中数组需要渲染的部分
  let v = lookup(data, token[1]);
  // 结果字符串
  let resultStr = "";
  // 遍历v,这里只考虑v为数组的情况
  // 重点:下面这个循环!遍历的是数据
  for (let i = 0, len = v.length; i < len; i++) {
    // '.'的识别,例子见最后
    resultStr += renderTemplate(token[2], {
      ...v[i],
      ".": v[i],
    });
  }
  return resultStr;
}
/**     .的例子
 *  <ul>
    {{#arr}}
    <li>
      {{.}}
    </li>
    {{/arr}}
  </ul>
 * 
 * let data={
 *  arr:['1','2','3']
 * }
 * 
 * 
 */

lookup:

/**
 * 让dataObj对象中可以用.符号的keyName属性
 * 例如:dataObj为:
 * {
 *  a:{
 *    b:{
 *      c:100
 *    }
 *   }
 * }
 * 那么lookup(dataObj,'a.b.c')结果为100
 */
export default function lookup(dataObj, keyName) {
  // 看看keyName中有没有.符号,但keyname不能为.
  if (keyName.indexOf(".") != -1 && keyName != ".") {
    // 如果有,拆分属性
    let keys = keyName.split(".");
    // 设置临时变量tmp,用于周转,一层层的找
    let tmp = dataObj;
    for (let i = 0, len = keys.length; i < len; i++) {
      // 更新tmp
      tmp = tmp[keys[i]];
    }
    return tmp;
  }
  return dataObj[keyName];
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值