mustache-4.1.0源码注解

由于1.0以后变化都不是很大,所有就直接看最新的源码解析了
https://cdn.bootcdn.net/ajax/libs/mustache.js/4.1.0/mustache.js

相对于以前 这个版本去掉了 !注释 和 < 获取新模板 渲染也去掉了

大概的方法

  • {{name}} ---- 会把数据中 name 转换为具体值
  • {{#list}} {{/list}} ----会把list中的数据选项展示
  • {{#flag}}flag为ture展示{{/flag}}{{^flag}}flag为false展示{{/flag}} ----判断语句
  • {{{}}} ---- 里面的内容不会转义
  • {{>name}} ----会从补充数据中获取数据(接口返回的数据不足以展示,需要补充数据)
  • {{=<% %>=}}
    ----修改 <% %> 为新切割符号

原理

大概原理是 把template模板 通过 对应的分隔符号 比如 {{ }} 转为为tokens;

["text", "

↵", 181, 188]
["^", "list", 194, 203, Array(5), 260]
["text", " 姓名:", 204, 221]
["name", "name", 221, 229]
["text", "年龄:", 229, 235]
["name", "age", 235, 242]
["text", "
↵", 242, 254]
["/", "list", 260, 269]
["#", "arr", 276, 284, Array(3), 321]

[type, value, start, scanner.pos]

  • 其中type 是这个 类型 text 为文本, name需要取数据中的键 ,# 是循环
  • value 数据中的键或者字符串
  • start 这个是当前数组所在字符串的位置
  • scanner.pos 扫描器的位置或者未结束位置

然后把tokens处理为 dom识别的字符串;

栗子:

      var template = `
        
{{name}}
{{#arr1}}

{{.}}

{{/arr1}} `; var data = { name: '张三', arr1: ['第一个', '第二个'] }; const str = Mustache.render(template, data);
image.png

image.png

源码里面有三个类:

  • Scanner;
  • Context;
  • Writer;

Scanner 扫描器

我的理解 Scanner 是 当 匹配符号比如 {{ 和 }} 匹配一段文本是分别 获取 匹配符号之外的文本。(其实跟以前版本正则类似)
比例: 我是{{name}},我爱我的{{contry}}。也爱你
通过Scanner类可以获取到 五段内容:

  1. 我是
  2. name
  3. ,我爱我的
  4. contry
  5. 。也爱你

Context 上下文

作用是用来处理data数据,掉push方法, 可以把循环中的token 存入 当前token中。便于查找对应的数据

Writer

提供render方法。 把模板转换为tokens。然后处理只有的tokens 通过数据转换为浏览器识别的字符串

html 测试



  
    
    
    
    Document
  
  
    

源码

// umd 写法: 兼容AMD和commonJS规范的同时,还兼容全局引用的方式
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = global || self, global.Mustache = factory());
}(this, (function () { 'use strict';



  // 定义 判断 数组方法
  var objectToString = Object.prototype.toString;
  var isArray = Array.isArray || function isArrayPolyfill (object) {
    return objectToString.call(object) === '[object Array]';
  };

  // 判断 函数方法
  function isFunction (object) {
    return typeof object === 'function';
  }

  // 获取 obj 的类型 
  function typeStr (obj) {
    return isArray(obj) ? 'array' : typeof obj;
  }

  // escapeRegExp是把匹配到的符号  前加一个反斜杠
  /**拓展: 
  * $1、$2、...、$99: 与 regexp 中的第 1 到第 99 个子表达式相匹配的文本
  * $&:与 regexp 相匹配的子串
  * $`:位于匹配子串左侧的文本。
  * $':位于匹配子串右侧的文本。
  * $$:直接量符号。
  * */
  function escapeRegExp (string) {
    return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
  }

  /**
   * 检查对象 是否具有给定属性的方法
   */
  function hasProperty (obj, propName) {
    return obj != null && typeof obj === 'object' && (propName in obj);
  }

  /**
   * 检测一个属性是否是对象的自有属性 (前提这个 对象不是null 并且不是 object)js中万物皆对象。
   */
  function primitiveHasOwnProperty (primitive, propName) {
    return (
      primitive != null
      && typeof primitive !== 'object'
      && primitive.hasOwnProperty
      && primitive.hasOwnProperty(propName)
    );
  }


  var regExpTest = RegExp.prototype.test;
  // 正则 test 方法
  function testRegExp (re, string) {
    return regExpTest.call(re, string);
  }

  var nonSpaceRe = /\S/;
  // 正则判断是 空白(包括 空格、换行、tab缩进等空白)
  function isWhitespace (string) {
    return !testRegExp(nonSpaceRe, string);
  }

  // 默认 需要转义的  符号
  var entityMap = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": ''',
    '/': '/',
    '`': '`',
    '=': '='
  };

  // 转义 特殊符号
  function escapeHtml (string) {
    return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
      return entityMap[s];
    });
  }

  var whiteRe = /\s*/; // 空格可能存在
  var spaceRe = /\s+/; // 最少有一个空格
  var equalsRe = /\s*=/; // 等于号前面可能存在空格
  var curlyRe = /\s*\}/; // 等于号前面最少存在一个空格
  var tagRe = /#|\^|\/|>|\{|&|=|!/; // 最少存在匹配里面一个符号

  // 处理模板为tokens
  function parseTemplate (template, tags) {
    // 如果没有传入模板,直接返回空数组
    if (!template)
      return [];
    var lineHasNonSpace = false; // 标志 当前行 是否有内容
    var sections = [];     // 栈(先进后出,后进先出)
    var tokens = [];       // 获取的tokens  (会切割得很细,一个空格也会是一个token)
    var spaces = [];       // 这个数据会保存 tokens 中 保存的是空格 的位置
    var hasTag = false;    // 当前行上有{{tag}}吗? 存在 解析标签 (stripSpace方法 用来去除空格用)
    var nonSpace = false;  // 当前行中是否有非空格字符? (stripSpace方法 用来去重空格用)
    var indentation = '';  // 跟踪使用它的标记的缩进  (记录有多少空格)
    var tagIndex = 0;      // 存储一行中遇到的标记数

    // 去除当前行的所有空白标记数组, 如果上面有{{#tag}} 并且 只有空格。
    function stripSpace () {
      if (hasTag && !nonSpace) {
        // 根据 保存的空格 位置, 删除tokens中的空格token
        while (spaces.length)
          delete tokens[spaces.pop()];
      } else {
        spaces = [];
      }
      // 把 匹配符号 标志重置为fals。 nonSpace为false
      hasTag = false;
      nonSpace = false;
    }


    var openingTagRe, closingTagRe, closingCurlyRe;
    // 根据传入的符号 设置标签
    function compileTags (tagsToCompile) {
      // 如果 匹配符号是 字符串, 说明要用自定义的解析符号 
      // 比如 <% %>  安装空字符串切个我一个对象 ["<%", ">%"]
      if (typeof tagsToCompile === 'string')
        tagsToCompile = tagsToCompile.split(spaceRe, 2);
      // tagsToCompile 不是一个数组,或者 是数组,但是不是两位直接报错
      if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)
        throw new Error('Invalid tags: ' + tagsToCompile);
      // 下面以解析符号获取正则表达式
      //  /\{\{\s*/   两个左大括号 后可能有空格 
      openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
      //  /\s*\}\}/   两个右大括号 前可能有空格
      closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
      //  /\s*\}\}\}/ 三个右大括号 前可能有空格
      closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
    }

    // 处理tags, 如果没有,那就用默认 tags ["{{", "}}"]
    compileTags(tags || mustache.tags);

    // 得到扫描器 Scanner类的示例
    var scanner = new Scanner(template);

    var start, type, value, chr, token, openSection;
    // 循环遍历 scanner
    while (!scanner.eos()) {
      // 获取 位置 pos 会根据scan 和scanUtil 变化
      start = scanner.pos;

      // 找到 {{ 开始符号前的文本
      value = scanner.scanUntil(openingTagRe);

      if (value) {
        // 把文本字符串 从第一个字符开始遍历 比如 "我是中国人   我爱中国"
        for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
          // 获取当前字符  比如 第一个 我
          chr = value.charAt(i);
          // 如果当前字符为空字符 当前tokens的长度 放到spaces数组中
          // tokens每一项放的都是一个 字符,sapces会记录哪几项是空格
          //  把缩进长度增加当前空白字符
          if (isWhitespace(chr)) {
            spaces.push(tokens.length);
            indentation += chr;
          } 
            // 不是空白字符   
            else {
            nonSpace = true; // 设置 非空标识 为 ture
            lineHasNonSpace = true; // 设置当前行 标识为 true(有内容)
            indentation += ' '; // 缩进长度加上 一个空格 
          }

          // 把当前字符 以 数组放入tokens中 并记录当前位置和结束位置(指针)
          tokens.push([ 'text', chr, start, start + 1 ]);
          start += 1;

          // 遇到回车
          if (chr === '\n') {
            // 调用方法 去重空白
            stripSpace();
            indentation = ''; // 由于调用了去空白方法 indentation 重置为空
            tagIndex = 0; // tagIndex也重置为0
            lineHasNonSpace = false; // 当前行标志也重置为 false
          }
        }
      }

      // 如果没有 {{ , 说明已经找完了 结束循环
      if (!scanner.scan(openingTagRe))
        break;

      // 如果在 {{ 里面的内容 设置  hasTag 为true
      hasTag = true;

      // 处理 循环 注释等其他 类型
      // 根据 /#|\^|\/|>|\{|&|=|!/ 获取 如果没有匹配到,那就是默认的数据类型 name
      // 可能是 循环开始#  循环结束\ 注释!不用转义{ 等
      type = scanner.scan(tagRe) || 'name';
      // 跳过空字符串
      scanner.scan(whiteRe);

      // 如果type 为 = 符号 用来更改后面的解析符号用的tags
      if (type === '=') {
        // value 保存 新的 匹配符号  比如 {{=<% %>=}} 获取到 value = "<% %>"
        value = scanner.scanUntil(equalsRe);
        scanner.scan(equalsRe);
        scanner.scanUntil(closingTagRe);
      } else if (type === '{') {
        // value 或 }}} 前的内容  里面的内容不要转义
        value = scanner.scanUntil(closingCurlyRe);
        scanner.scan(curlyRe);
        scanner.scanUntil(closingTagRe);
        // 并且把类型重置为 & 符号
        type = '&';
      } else {
        // 其他情况 获取结束符号}} 前的文本
        value = scanner.scanUntil(closingTagRe);
      }

      // 匹配结束符号
      if (!scanner.scan(closingTagRe))
        throw new Error('Unclosed tag at ' + scanner.pos);

      // 如果 type 为 > 需要多记录 三个 参数
      // token [类型, 值, 开始位置, scanner的位置, 空白字符, 一行中遇到的标记数, 当前token有内容]
      if (type == '>') {
        token = [ type, value, start, scanner.pos, indentation, tagIndex, lineHasNonSpace ];
      } else {
        // 设置每一项token [类型, 值, 开始位置, scanner的位置]
        token = [ type, value, start, scanner.pos ];
      }
      
      // 标记数+1
      tagIndex++;
      // tokens 存入token
      tokens.push(token);

      // 如果遇到循环 或者判断 if else  需要把当前token 放入栈中(便于后面判断是否循环或判断结束)
      if (type === '#' || type === '^') {
        sections.push(token);
      } else if (type === '/') {
        // 遇到循环或判断  结束符号 取出 最后进的token
        openSection = sections.pop();

        // 没有内容报错
        if (!openSection)
          throw new Error('Unopened section "' + value + '" at ' + start);
        // 如果内容 [type, value, start, scanner.pos] 如果值不相等报错
        if (openSection[1] !== value)
          throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
      } else if (type === 'name' || type === '{' || type === '&') {
        // 如果type 为上面 的  那设置标志 为有内容
        nonSpace = true;
      } else if (type === '=') {
        // 为下一次循环 设置 新标签 可能改为了 <% %> 
        compileTags(value);
      }
    }

    // 循环结束 调用方法去除空白
    stripSpace();

    // 循环结束  判断栈中是否还有  内容
    openSection = sections.pop();

    if (openSection)
      throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);

    return nestTokens(squashTokens(tokens));
  }


  /**
   * 将给定“tokens”数组中连续文本标记的值合并为单个标记。
   * 把相邻的token 切 type都是text类型的文本合并为一个token
   */
  function squashTokens (tokens) {
    var squashedTokens = []; // 压缩后的数组

    var token, lastToken; // 当前token  和上一次 token
    for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
      token = tokens[i];
      // [type, value, start, scanner.pos]

      if (token) {
        // 如果当前token类型为 text 。 并且上一个token(现在的lastToken) 类型也是text
        // 那么就把两个token合并
        // 这里的处理 把value 现在, scanner.pos 位置为现在token的位置
        if (token[0] === 'text' && lastToken && lastToken[0] === 'text') {
          lastToken[1] += token[1];
          lastToken[3] = token[3];
        } else {
          // 第一项 token 存起来, 并且赋值给lastToken
          // 或者现在的token和上一个token type不一样
          squashedTokens.push(token);
          lastToken = token;
        }
      }
    }

    return squashedTokens;
  }


  /**
   * 处理 # 开始 的循环接口。处理为嵌套的token
   * 把 type 为 # 的开始token ,结束为 '/'结束的token。 这一堆token作为数组,作为#token的第五项
   * 
   * 或者 处理  ^ /  类似于 if else 的判断
   * 
   * 这里要注意  收集器 的作用主要是 改变 一个指向。如要 # 就要把收集器指向 当前token的 第五项
   * 当遇到 / 说明循环结束了。需要把收集器指向上一级的token的第五项
   * 循环一直,只到没有循环为止,指向最初的token;
   */
  function nestTokens (tokens) {
    var nestedTokens = []; // 嵌套tokens
    var collector = nestedTokens; // 收集器 指向每一次需要收集的数组
    var sections = [];// 栈数组 用于 保存有多少次循环, 并存在循环的主token

    var token, section;
    for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
      token = tokens[i];

      switch (token[0]) {
        // 如果type 为 # ^ 那就是嵌套开始项 或者 判断
        // [type, value, start, scanner.pos, [嵌套token]]
        case '#':
        case '^':
          // 收集器push 进token
          collector.push(token);
          // 栈中 推入当前token 这个是类似一个父token
          sections.push(token);
          // 收集器 现在指向 当前token 第五项并且为空数组
          // 只有遇到的token都要放入这个空数组中
          collector = token[4] = [];
          break;
        case '/':
          // 如果遇到/ 那么就是 循环或判断结束了。 
          // 结束了的话,把最后的一次栈中的token 取出来
          section = sections.pop();
          // 把 当前token 的scanner.pos 给 父token的第六项 
          section[5] = token[2];
          // 判断栈中还有没有数据。没有的话,就是初始nestedTokens;否则,
          // 收集器指针 需要指向上一级父token的第五位
          collector = sections.length > 0 ? sections[sections.length - 1][4] : nestedTokens;
          break;
        default:
          // 循环中 把每一个token 放入收集器中 (除开 循环或判断的情况)
          collector.push(token);
      }
    }

    return nestedTokens;
  }

  /**
   * 扫描器
   * 模板解析器用于在模板字符串中查找令牌的简单字符串扫描仪
   * 获取字符串,并设置尾部,尾巴会向后减少。pos是当前位置
   */
  function Scanner (string) {
    this.string = string;
    this.tail = string;
    this.pos = 0;
  }

  // 判断是否到尾部
  Scanner.prototype.eos = function eos () {
    return this.tail === '';
  };

  /**
   * 为了跳过匹配符号
   * 返回匹配的匹配的符号,如果没有返回空字符串
   * 比如 re为 {{   找到的话就返回 {{  并且尾巴向后移动两位,pos位置也加2
   */
  Scanner.prototype.scan = function scan (re) {
    var match = this.tail.match(re);
    // 没有匹配直接返回空字符串

    if (!match || match.index !== 0)
      return '';
    

    var string = match[0];
    
    // 尾巴 向后移动 匹配符号的长度

    this.tail = this.tail.substring(string.length);
    // 位置加 上匹配符号的长度
    this.pos += string.length;

    return string;
  };

  /**
   * 为了找到 匹配符号前的文本
   * 如果有匹配的符号,返回匹配符号之前的文本,否则就是没有匹配到,返回剩下的尾巴
   * 比如 这里是{{name}}的家  re为 {{  会返回 这里是
   */
  Scanner.prototype.scanUntil = function scanUntil (re) {
    // 得到匹配符号在尾巴中的位置(尾巴会变化,所有位置也会在变化)
    var index = this.tail.search(re), match;

    switch (index) {
      // -1说明没有匹配到特殊符号,说明已经扫描完了。直接返回剩下的文本。比把尾巴置空
      case -1:
        match = this.tail;
        this.tail = '';
        break;
        // 特殊符号刚好在开始地方,那匹配符号前 就没有文本。匹配到的就是空字符串
      case 0:
        match = '';
        break;
        // 其他情况 直接获取匹配符号前的文本,并把尾巴向后移动到匹配的地方(这个时候后面会用scan函数跳过匹配符号)
      default:
        match = this.tail.substring(0, index);
        this.tail = this.tail.substring(index);
    }

    // 位置 需要加上匹配的长度 ,并返回匹配的文本 
    this.pos += match.length;

    return match;
  };

  /**
   * 
   * view 类 数据 比如 {{name: 'zs', age: 14}}
   * 把数据作为参数 实例化Context 。便于 创建新的数据 并且根据树结构查找数据
   */
  function Context (view, parentContext) {
    this.view = view;
    // 缓存  对象  .  便于 循环  通过 {{.}} 找到值
    this.cache = { '.': this.view };
    // 设置父元素为 传入的第二个参数 (以便于 循环或者判断用,避免循环里面有变量名和根变量名重复; 比如 {name: 'zs', list:[{name: 'ls'}, {name: 'ww'}]})
    this.parent = parentContext;
  }

  // 使用给定视图创建一个新上下文,并将此上下文作为父级
  // push 方法 处理循环或判断用 ,创建一个新的contenxt实例,得到新的数据与模板
  Context.prototype.push = function push (view) {
    return new Context(view, this);
  };

  // 返回此上下文中给定名称的值,如果此上下文视图中缺少该值,则遍历上下文层次结构。(用来查找数据中的值)
  Context.prototype.lookup = function lookup (name) {
    // 首先拿到缓存
    var cache = this.cache;
    // 定义查找的内容

    var value;
    // 如果要查找的 内容在缓存中  赋值为value
    if (cache.hasOwnProperty(name)) {
      value = cache[name];
    } else {
      // context 初始定义为 当前对象,下面可能会改变
      // 增加一个标识 lookupHit 是否查找命中 
      // intermediateValue 中间值
      var context = this, intermediateValue, names, index, lookupHit = false;

      while (context) {
        // 如果要查找的是 a.b.c 类似 的数据
        if (name.indexOf('.') > 0) {
          // 首先把 数据赋值为 intermediateValue 比如 { a: { b: c: 'haha' } }
          intermediateValue = context.view;
          // names 把 a.b.c 拆分为 [a.b.c]
          names = name.split('.');
          index = 0;
          //  循环 names 数组 ,并分别从intermediateValue中查找到值,并重新赋值为intermediateValue
          //  比如上面最后会得到 intermediateValue 为 haha
          while (intermediateValue != null && index < names.length) {
            if (index === names.length - 1)
              lookupHit = (
                hasProperty(intermediateValue, names[index])
                || primitiveHasOwnProperty(intermediateValue, names[index])
              );

            intermediateValue = intermediateValue[names[index++]];
          }
        } else {
          // 直接返回匹配的值
          intermediateValue = context.view[name];
          // 对象自身属性中是否具有指定的属性(也就是,是否有指定的键)
          lookupHit = hasProperty(context.view, name);
        }

        // 如果有命中  把中间值赋值为value
        if (lookupHit) {
          value = intermediateValue;
          break;
        }

        context = context.parent;
      }

      // 缓存 name-value 的值
      cache[name] = value;
    }
    // 如果查找到的内容是 函数,然后执行到,赋值给value
    if (isFunction(value))
      value = value.call(this.view);

    return value;
  };

   /**
   * 提供解析模板为tokens 然后把tokens 转为 dom字符串
   * 根据tokens 转换为 字符串,并且缓存它
   */
  function Writer () {
    this.templateCache = {
      _cache: {},
      set: function set (key, value) {
        this._cache[key] = value;
      },
      get: function get (key) {
        return this._cache[key];
      },
      clear: function clear () {
        this._cache = {};
      }
    };
  }

  // 清空缓存
  Writer.prototype.clearCache = function clearCache () {
    if (typeof this.templateCache !== 'undefined') {
      this.templateCache.clear();
    }
  };

  // 解析和缓存给定的“模板”,并返回从解析生成的令牌数组。
  Writer.prototype.parse = function parse (template, tags) {
    // 拿到缓存
    var cache = this.templateCache;
    // 获取 改变后的 缓存key
    var cacheKey = template + ':' + (tags || mustache.tags).join(':');

    var isCacheEnabled = typeof cache !== 'undefined';
    // 获取从缓存中获取tokens 或在没有
    var tokens = isCacheEnabled ? cache.get(cacheKey) : undefined;

    if (tokens == undefined) {
      // 如果缓存中没有  需要重新 获取并缓存起来
      tokens = parseTemplate(template, tags);
      isCacheEnabled && cache.set(cacheKey, tokens);
    }
    return tokens;
  };

  /**
   * 渲染函数 
   * template 为模板
   * view 为数据
   * partials 为补充模板 可以为对象也可以为函数
   * config 为补充参数  里面可能有 自定义的解析符号tags  转义规则escape
   */
  Writer.prototype.render = function render (template, view, partials, config) {
    // 获取自定义转义符号 tags
    var tags = this.getConfigTags(config);
    // 获取 处理后的 tokens
    var tokens = this.parse(template, tags);
    // 把数据 view 用Context实例化, 便于储存数据和查找
    var context = (view instanceof Context) ? view : new Context(view, undefined);
    return this.renderTokens(tokens, context, partials, template, config);
  };

  // 递归函数  用于处理tokens  处理为dom字符串
  Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, config) {
    var buffer = '';

    var token, symbol, value;
    for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
      value = undefined;
      // 获取每一个token
      token = tokens[i];
      // 获取token 的type 类型 
      symbol = token[0];

      // 不同的类型 用不同的方法处理
      // 循环
      if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate, config);
      // 判断
      else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate, config);
      // partials 提供 新的数据(接口返回的数据不够,需要自定义某些数据)
      else if (symbol === '>') value = this.renderPartial(token, context, partials, config);
      // 不用转义的
      else if (symbol === '&') value = this.unescapedValue(token, context);
      // 需要转义
      else if (symbol === 'name') value = this.escapedValue(token, context, config);
      // 普通文本
      else if (symbol === 'text') value = this.rawValue(token);

      if (value !== undefined)
        buffer += value;
    }

    return buffer;
  };

  // 如果是# 那这个token 就是循环的token  或者判断中的 if 语句
  // 比如 ["#", "list", 194, 203, Array(5), 260]
  Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate, config) {
    var self = this;
    var buffer = '';
    // 数据中查找 list 对应的 数据
    var value = context.lookup(token[1]);

    // 附属渲染  获取  顶层渲染的数据  self 当前 最顶层
    function subRender (template) {
      return self.render(template, context, partials, config);
    }
    // 如果数据没有则跳过

    if (!value) return;

    // 如果是 数组
    // 比如  {list: [{name:'zs'},{name: 'ls'}]}
    // value 为[{name:'zs'}, {name: 'ls'}, {name: 'zl'}]
    if (isArray(value)) {
      for (var j = 0, valueLength = value.length; j < valueLength; ++j) {
        // 递归 把 Array(5) 作为token, 每一项 {name: 'xx'} 作为数据
        // 调用 context.push 方法传入 对应的数据 value[j]
        buffer += this.renderTokens(token[4], context.push(value[j]), partials, originalTemplate, config);
      }
      //如果 找到的 value 为对象或字符串
      // {list: {name:'zs', age:14}} 或者 {list: 'abcd'} 或者 {list: 1234}
      // value 为 {name:'zs', age:14} 或 'abcd' 或1234
    } else if (typeof value === 'object' || typeof value === 'string' || typeof value === 'number') {
      // 也是递归 把 Array(5)作为token, 直接把当前value作为数据
      // 调用 context.push 方法传入 对应的数据 value
      buffer += this.renderTokens(token[4], context.push(value), partials, originalTemplate, config);
    // 如果找到的函数
    /**
     *  arr: ()=> (template, fn)=>{
     *     return fn(template)
     *   }
     * 
     *  template  == originalTemplate.slice(token[3], token[5]) 为循环内的位解析字符串
     * fn == subRender 用根 数据渲染当前模板
     */
    } else if (isFunction(value)) {
      // 如果 原始模板不是字符串 则报错
      if (typeof originalTemplate !== 'string')
        throw new Error('Cannot use higher-order sections without the original template');

      // 返回一个自定义函数, 原函数匹配的字符串为第一个参数 ,subRender为第二个参数(以根数据来渲染当前模板)
      value = value.call(context.view, originalTemplate.slice(token[3], token[5]), subRender);

      if (value != null)
        buffer += value;
    } else {
      // 其他情况 直接调用 递归处理 数据 就是当前 context
      // 是 判断语句,如果找到就直接渲染拼接 ,没有会返回空
      buffer += this.renderTokens(token[4], context, partials, originalTemplate, config);
    }
    return buffer;
  };

  // 判断 类似于 if else  中的 else 
  // {{# flag}}flag为ture显示{{/flag}}{{^flag}}flag为false显示{{/flag}}
  Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate, config) {
    // 得到 value 需要判断的内容
    var value = context.lookup(token[1]);

    // 如果没有 或者是个空数组  
    if (!value || (isArray(value) && value.length === 0))
      return this.renderTokens(token[4], context, partials, originalTemplate, config);
  };

  // 这个函数为啥要这么处理,,,我没有尝试出来。。。。
  Writer.prototype.indentPartial = function indentPartial (partial, indentation, lineHasNonSpace) {

    var filteredIndentation = indentation.replace(/[^ \t]/g, ''); // 除了空格 和 制表符TAB 的其他空白 都去掉
    
    var partialByNl = partial.split('\n'); // 把模板 按 换行符 切割为数组
    
    for (var i = 0; i < partialByNl.length; i++) {
      // 模板有内容 并且  (没有内容 或者  不是第一项)
      if (partialByNl[i].length && (i > 0 || !lineHasNonSpace)) {
        // 模板 替换为  处理后的filteredIndentation + 当前模板
        partialByNl[i] = filteredIndentation + partialByNl[i];
      }
    }
    // 处理之后把 模板 按 换行符 拼接起来
    return partialByNl.join('\n');
  };

  // 根据 补充数据 partials 获取数据 比如  Mustache.render("
{{>abc}}
", data, { abc: '123', bbb: 'ttt' }); // 栗子: partials = {abc: '123', bbb: 'ttt' } // 比如 token [">", "abc", 12, 42, " ", 0, true] Writer.prototype.renderPartial = function renderPartial (token, context, partials, config) { // 没有数据源 就返回 if (!partials) return; // 获取自动以的 标签tags var tags = this.getConfigTags(config); // 先判断 partials 是不是 函数 ,函数直接调用,否则直接返回 新模板 var value = isFunction(partials) ? partials(token[1]) : partials[token[1]]; if (value != null) { // token token [类型, 值, 开始位置, scanner的位置, 空白字符, 一行中遇到的标记数, 当前token有内容] var lineHasNonSpace = token[6]; // 当前token 有内容 标志 var tagIndex = token[5]; // 一行中遇到的标记数 (碰到换行符 会重置为0) var indentation = token[4]; // 空白字符 var indentedValue = value; // 比如: 123 // 标记数为0 (新起的一行 )并且有内容 if (tagIndex == 0 && indentation) { indentedValue = this.indentPartial(value, indentation, lineHasNonSpace); } // 获取处理后的tokens 比如 ['text', 123, 0, 3] var tokens = this.parse(indentedValue, tags); return this.renderTokens(tokens, context, partials, indentedValue, config); } }; // 获取 token里面的值 不用转义 Writer.prototype.unescapedValue = function unescapedValue (token, context) { var value = context.lookup(token[1]); if (value != null) return value; }; // 获取 token里面的值 并 转义 (转义规则可能由用户传入) Writer.prototype.escapedValue = function escapedValue (token, context, config) { // 获取 用户传入的转义规则 没有 就用默认的转义规则 var escape = this.getConfigEscape(config) || mustache.escape; var value = context.lookup(token[1]); if (value != null) // 如果得到的内容 是数字 或者就 等于转义后的字符串 就直接返回当前内容, 否则需要调用 escape方法转义后返回 return (typeof value === 'number' && escape === mustache.escape) ? String(value) : escape(value); }; // 获取 未加工的值 (保存的 文本) Writer.prototype.rawValue = function rawValue (token) { return token[1]; }; // 获取config 的tags (自定义标签 tags) Writer.prototype.getConfigTags = function getConfigTags (config) { // 是数组返回 本身 if (isArray(config)) { return config; } // 如果是对象,返回 对象的tags属性 else if (config && typeof config === 'object') { return config.tags; } // 否则 返回undefined else { return undefined; } }; // 获取 config 的转义符号对象 (自定义转义规则) Writer.prototype.getConfigEscape = function getConfigEscape (config) { if (config && typeof config === 'object' && !isArray(config)) { return config.escape; } else { return undefined; } }; var mustache = { name: 'mustache.js', version: '4.1.0', tags: [ '{{', '}}' ], clearCache: undefined, escape: undefined, parse: undefined, render: undefined, Scanner: undefined, Context: undefined, Writer: undefined, /** * 允许用户通过为对象提供set、get和clear方法来覆盖默认的缓存策略。也可以通过将缓存设置为文字“undefined”来禁用缓存。 */ set templateCache (cache) { defaultWriter.templateCache = cache; }, /** * 获取缓存 */ get templateCache () { return defaultWriter.templateCache; } }; // 实例化 Writer var defaultWriter = new Writer(); /** * 清除缓存 */ mustache.clearCache = function clearCache () { return defaultWriter.clearCache(); }; /** * 把模板处理为tokens */ mustache.parse = function parse (template, tags) { return defaultWriter.parse(template, tags); }; /** * 暴露的render 方法为实例化Writer之后的render方法 */ mustache.render = function render (template, view, partials, config) { // template 模板必须为字符串 if (typeof template !== 'string') { throw new TypeError('Invalid template! Template should be a "string" ' + 'but "' + typeStr(template) + '" was given as the first ' + 'argument for mustache#render(template, view, partials)'); } return defaultWriter.render(template, view, partials, config); }; // escape 作用是把特殊符号转移 mustache.escape = escapeHtml; // 导出三个类主要用于测试,但也用于高级用途 mustache.Scanner = Scanner; mustache.Context = Context; mustache.Writer = Writer; return mustache; })));

你可能感兴趣的:(mustache-4.1.0源码注解)