由于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);
源码里面有三个类:
- Scanner;
- Context;
- Writer;
Scanner 扫描器
我的理解 Scanner 是 当 匹配符号比如 {{ 和 }} 匹配一段文本是分别 获取 匹配符号之外的文本。(其实跟以前版本正则类似)
比例: 我是{{name}},我爱我的{{contry}}。也爱你
通过Scanner类可以获取到 五段内容:
- 我是
- name
- ,我爱我的
- contry
- 。也爱你
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;
})));