mustache.js(3.0.0版本) 是一个javascript前端模板引擎。官方文档(https://github.com/janl/mustache.js)
根据官方介绍:Mustache可以被用于html文件、配置文件、源代码等很多场景。它的运行得益于扩展一些标签在模板文件中,然后使用一个hash字典或对象对其进行替换渲染操作。
基本语法如下:
1. {{ keyName }}: 读取属性值, 如果有html标签的话,会被转义。
2. {{{ keyName }}}: 读取属性值且原样输出,即html不进行转义。
3. {{ #keyName }} {{ /keyName }}: 用于遍历。
4. {{ ^keyName }} {{ /keyName }}: 反义数据,当keyName不存在、或为null,或为false时会生效。可以理解相当于我们js中的 !(非)。
5. {{.}}: 用于遍历数组。
6. {{ !comments }}: 用于注释。
7. Partials: 使用可重用的模板,使用方式:{{> 变量}}。
1. 变量 {{ keyName }} 或 {{{ keyName }}}
标签最主要是通过一个变量来使用。比如 {{ keyName }}标签在模板中会尝试查找keyName这个变量在当前的上下文中,如果上下文中不存在keyName变量,那么它会通过递归的方式依次查找它的父级元素,依次类推... 如果最顶级的上下文中依然找不到的话,那么该keyName变量就不会被渲染。否则的话,keyName标签就会被渲染。
如果变量中存在html标签会被转义的。因此如果我们不想html标签转义的话,我们可以使用三个花括号 {{{ keyName }}}.
比如如下列子:
项目基本结构如下:
|--- mustache 文件夹 | |--- index.html | |--- mustache.js (库文件)
基本代码如下所示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": {
"sex": " male ",
"age": "31",
"marriage": 'single'
}
}
var tpl = ' {{name}}
';
var html = Mustache.render(tpl, data);
console.log(html); // 打印如下: <a>kongzhi<a>
script>
body>
html>
如上可以看到,我们name字段,存在a标签中的 < 或 > 被转义了,如果我们想它们不需要转义的话,我们需要使用三个花括号 {{{}}}。如下代码输出:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": {
"sex": " male ",
"age": "31"
}
}
var tpl = ' {{{name}}}
';
var html = Mustache.render(tpl, data);
console.log(html); // 打印
script>
body>
html>
当然如果我们上面不想使用三个花括号的话,我们也可以使用 & 告诉上下文不需要进行转义。比如 {{ &name }} 这样的,如上面的三个花括号 {{{ name }}}, 我们也可以改成 {{ &name }}; 效果是一样的。
2. 块
2.1 {{#keyName}} {{/keyName}}
{{#keyName}} 是一个标签,它的含义是块的意思。所谓块就是渲染一个区域的文本一次或多次。
块的开始形式是:{{#keyName}},结束形式是:{{/keyName}}。
我们可以使用该 {{#keyName}} {{/keyName}} 标签来遍历一个数组或对象。如下代码所示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": {
"sex": " male ",
"age": "31",
"marriage": 'single'
}
}
var tpl = `{{ #msg }}<div>{{sex}}</div>{{age}}</div><div>{{marriage}}</div>{{ /msg }}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 male 31single
script>
body>
html>
注意:如果上面的 msg 是一个布尔值 false的话,即 msg: false, 那么 tpl 模板不会被渲染。最后html为 ''; 但是如果 msg 的值是 msg: {} 这样的话,那么tpl会渲染,只是没有值而已,最后输出:'
' 这样的。
Function
当keyName的值是一个可以被调用的对象,或者是一个函数的话,那么该函数会被调用并且传递标签包含的文本进去。如下代码所示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": {
"sex": " male ",
"age": "31",
"marriage": 'single'
},
"wrapped": function() {
return function(text, render) {
return '' + render(text) + ''
}
}
}
var tpl = `{{#wrapped}} {{name}} is men {{/wrapped}}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 kongzhi is men
script>
body>
html>
如果该变量的值也是一个函数的话,那么我们也可以迭代上下文的数组。如下代码演示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"msg": [
{ 'firstName': 'kongzhi111', "lastName": 'kong' },
{ 'firstName': 'kongzhi222', "lastName": 'zhi' }
],
"name": function() {
return this.firstName + " " + this.lastName;
}
}
var tpl = `{{#msg}} {{name}} {{/msg}}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 kongzhi111 kong kongzhi222 zhi
script>
body>
html>
2.2 {{ ^keyName }} {{ /keyName }}
{{ ^keyName }} {{ /keyName }} 的含义是:取相反的数据。当keyName不存在、或为null,或为false时会生效。可以理解相当于我们js中的 !(非) 如下代码所示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": null // 为null, undefined, '' 或 false,数据才会被渲染
}
var tpl = `{{ ^msg }}<div>暂无数据</div>{{ /msg }}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 暂无数据
script>
body>
html>
2.3 {{.}}
{{.}} 也是可以遍历一个数组。
如下代码:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": ['111', '222', '333']
}
var tpl = `{{#msg}} {{.}} * {{/msg}}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 111 * 222 * 333 *
script>
body>
html>
3. {{ !comments }}
{{ !comments }} 可以理解为代码注释。良好的编码习惯,都会有一些注释来辅佐。同样在我们的 mustache中也存在注释的标签。
下面我们来看看如何使用注释:
如下代码:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": 'kongzhi'
}
var tpl = `<div>{{name}}</div>{{ ! 这是一段注释 }}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 kongzhi
script>
body>
html>
4. Partials的使用
Partials的含义是:使用可重用的模板,使用方式:{{> 变量}}. 相当于 include 的意思。
可以查看如下demo演示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
var data = {
"name": "kongzhi",
"msg": ['111']
}
var tpl = `{{#msg}} {{.}} * <div>{{name}}</div> {{/msg}}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 111 * kongzhi
/*
* 如上我们的tpl模板文件中引入了 {{name}} 模块,但是该模块在其他的地方
* 也使用到了,因此我们想让他当做一个模板定义,在需要的地方 引用进来。因此我们如下这样做了:
var data = {
"name": "kongzhi",
"msg": ['111']
}
var temp = `{{name}}`;
var tpl = `{{#msg}} {{.}} * {{> user }} {{/msg}}`;
var html = Mustache.render(tpl, data, {
user: temp
});
console.log(html); // 打印 111 * kongzhi
*/
script>
body>
html>
5. 设置分割符号
有些时候我们想修改一下 mustache默认的标签分割符号 {{}}. mustache也允许我们这样做的。并且修改的方法很简单。
比如说我们把分隔符改成 {% %} 这样的 ,或者 {{% %}}这样的,也是可以的。我们只需要 Mustache.render 方法中传递第四个参数,并且模板也需要改成这样的分割符号,如下代码所示:
DOCTYPE html>
<html>
<head>
<title>mustache--demotitle>
<meta charset="utf-8">
<script type="text/javascript" src="./mustache.js">script>
head>
<body>
<script type="text/javascript">
console.log(Mustache);
var data = {
"name": "kongzhi",
"msg": ['111']
}
var tpl = `{{%#msg%}} {{%.%}} * <div>{{%name%}}</div> {{%/msg%}}`;
var html = Mustache.render(tpl, data, {}, [ '{{%', '%}}' ]);
console.log(html); // 打印 111 * kongzhi
script>
body>
html>
如上可以看到,我们在 Mustache.render 方法中,传递了第四个参数为 [ '{{%', '%}}' ],因此在模板中我们的开始标签需要使用 '{{%'这样的,在结束标签使用 '%}}' 这样的即可。或者改成任何其他自己喜欢的分隔符都可以,关键设置第四个参数和模板要对应起来。
二:Mustache.js 源码分析
我们首先引入 mustache库文件后,然后我们在页面上打印 console.log(Mustache); 看到打印如下信息:
{
Context: fn(view, parentContext),
Scanner: fn,
Writer: fn,
clearCache: fn,
escape: function escapeHtml(){},
name: "mustache.js",
parse: fn(template, tags),
render: fn(template, view, partials, tags),
tags: ["{{", "}}"],
to_html: fn(template, view, partials, send),
version: "3.0.0"
}
如上我们可以看到我们的 Mustache.js 库对外提供了很多方法。下面我们来分析下源码:
1. 入口结构如下:
(function defineMustache (global, factory) {
/*
如下判断支持 CommonJS 规范引入文件 或 AMD 规范引入文件,或直接引入js文件,
Mustache 就是我们的全局变量对外暴露。
*/
if (typeof exports === 'object' && exports && typeof exports.nodeName !== 'string') {
factory(exports); // CommonJS
} else if (typeof define === 'function' && define.amd) {
define(['exports'], factory); // AMD
} else {
global.Mustache = {};
factory(global.Mustache); // script, wsh, asp
}
}(this, function mustacheFactory(mustache) {
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';
}
// 判断类型
function typeStr (obj) {
return isArray(obj) ? 'array' : typeof obj;
}
function escapeRegExp (string) {
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
}
// 判断对象是否有该属性
function hasProperty (obj, propName) {
return obj != null && typeof obj === 'object' && (propName in obj);
}
// 判断原型上是否有该属性
function primitiveHasOwnProperty (primitive, propName) {
return (
primitive != null
&& typeof primitive !== 'object'
&& primitive.hasOwnProperty
&& primitive.hasOwnProperty(propName)
);
}
// Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
// See https://github.com/janl/mustache.js/issues/189
var regExpTest = RegExp.prototype.test;
function testRegExp (re, string) {
return regExpTest.call(re, string);
}
var nonSpaceRe = /\S/;
function isWhitespace (string) {
return !testRegExp(nonSpaceRe, string);
}
// 对< > 等进行转义
var entityMap = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`',
'=': '='
};
// 转换html标签进行转义操作
function escapeHtml (string) {
return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
return entityMap[s];
});
}
var whiteRe = /\s*/; // 匹配0个或多个空白
var spaceRe = /\s+/; // 匹配至少1个或多个空白
var equalsRe = /\s*=/; // 匹配字符串 "=",且前面允许0个或多个空白符,比如 "=" 或 " =" 这样的。
var curlyRe = /\s*\}/; // 匹配 "}" 或 " }"
var tagRe = /#|\^|\/|>|\{|&|=|!/; // 匹配 '#', '^' , '/' , '>' , { , & , = , ! 任何一个字符
// ...... 代码略
mustache.name = 'mustache.js';
mustache.version = '3.0.0';
mustache.tags = [ '{{', '}}' ];
// ..... 代码略
mustache.escape = escapeHtml;
// Export these mainly for testing, but also for advanced usage.
mustache.Scanner = Scanner;
mustache.Context = Context;
mustache.Writer = Writer;
mustache.clearCache = function clearCache () {};
mustache.parse = function parse (template, tags) {};
mustache.render = function render (template, view, partials, tags) {};
mustache.to_html = function to_html (template, view, partials, send) {};
}));
如上代码内部的一些工具函数,稍微了解下就好。及把很多函数挂载到 mustache对外暴露的对象上。因此我们上面打印 console.log(Mustache); 就可以看到 该对象下有很多方法和属性,如上就是对外暴露的。
下面我们可以根据demo来分析,如下demo代码:
var data = {
"name": "kongzhi",
"msg": ['111']
}
var tpl = `{{#msg}} {{.}} * {{name}} {{/msg}}`;
var html = Mustache.render(tpl, data);
console.log(html); // 打印 111 * kongzhi
从上面我们打印的 console.log(Mustache) 可知:该全局变量有很多方法,其中就有一个 render方法,该方法接收4个参数,如下代码:Mustache.render(tpl, data, ,partials, tags); 各个参数含义分别如下:tpl(模板),data(模板数据),partials(可重用的模板), tags(可自定义设置分隔符);
如上我们只传入两个参数,其中 tpl 是必须传递的参数,否则不传会报错。因此会调用内部 render() 方法,方法代码如下所示:
mustache.render = function render (template, view, partials, tags) {
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, tags);
};
然后返回 defaultWriter.render(template, view, partials, tags); 函数,defaultWriter 是 Writer方法的实列,因此它有Writer对象中所有的属性和方法。从源码中如下代码可知:
var defaultWriter = new Writer();
Write 函数原型上有如下方法:
function Writer () {
this.cache = {};
}
Writer.prototype.clearCache = function clearCache () {
this.cache = {};
};
Writer.prototype.parse = function parse (template, tags) {
// ...
};
Writer.prototype.render = function render (template, view, partials, tags) {
// ...
}
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate) {
// ...
}
Writer.prototype.renderSection = function renderSection (token, context, partials, originalTemplate) {
// ...
}
Writer.prototype.renderInverted = function renderInverted (token, context, partials, originalTemplate) {
// ...
}
Writer.prototype.renderPartial = function renderPartial (token, context, partials) {
// ...
}
Writer.prototype.unescapedValue = function unescapedValue (token, context) {
// ...
}
Writer.prototype.escapedValue = function escapedValue (token, context) {
// ...
}
Writer.prototype.rawValue = function rawValue (token) {
// ...
}
下面我们最主要看 Writer.prototype.render 中的方法吧,代码如下所示:
/*
@param {template} 值为:"{{#msg}} {{.}} * {{name}} {{/msg}}";
@param {view} 值为:{name: 'kongzhi', msg: ['111']}
*/
Writer.prototype.render = function render (template, view, partials, tags) {
var tokens = this.parse(template, tags);
var context = (view instanceof Context) ? view : new Context(view);
return this.renderTokens(tokens, context, partials, template);
};
如上代码,我们首先会调用 this.parse(template, tags); 方法来解析该模板代码; 那么我们就继续看 parse 代码如下:
/*
@param {template} 值为:"{{#msg}} {{.}} * {{name}} {{/msg}}";
@param {tags} 值为:undefined
*/
Writer.prototype.parse = function parse (template, tags) {
var cache = this.cache;
/*
template = "{{#msg}} {{.}} * {{name}} {{/msg}}";
tags的默认值:从源码可以看到:mustache.tags = [ '{{', '}}' ]; 因此:[ '{{', '}}' ].join(':') = "{{:}}";
因此:cacheKey的值返回 "{{#msg}} {{.}} * {{name}} {{/msg}}:{{:}}"
*/
var cacheKey = template + ':' + (tags || mustache.tags).join(':');
// 第一次 cache 为 {}; 所以 第一次 tokens 返回undefined;
var tokens = cache[cacheKey];
/*
因此会进入 if语句内部,然后会调用 parseTemplate 模板进行解析,解析完成后,把结果返回 tokens = cache[cacheKey];
*/
if (tokens == null)
tokens = cache[cacheKey] = parseTemplate(template, tags);
// 最后把token的值返回
return tokens;
};
如上代码解析,我们来看下 parseTemplate 函数代码如下:
/*
@param {template} 的值为:"{{#msg}} {{.}} * {{name}} {{/msg}}";
@param {tags} 的值为:undefined
*/
function parseTemplate (template, tags) {
// 没有模板,直接返回 [];
if (!template)
return [];
var sections = [];
var tokens = [];
var spaces = [];
var hasTag = false;
var nonSpace = false;
// Strips all whitespace tokens array for the current line
// if there was a {{#tag}} on it and otherwise only space.
function stripSpace () {
if (hasTag && !nonSpace) {
while (spaces.length)
delete tokens[spaces.pop()];
} else {
spaces = [];
}
hasTag = false;
nonSpace = false;
}
var openingTagRe, closingTagRe, closingCurlyRe;
function compileTags (tagsToCompile) {
if (typeof tagsToCompile === 'string')
tagsToCompile = tagsToCompile.split(spaceRe, 2);
if (!isArray(tagsToCompile) || tagsToCompile.length !== 2)
throw new Error('Invalid tags: ' + tagsToCompile);
openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
}
compileTags(tags || mustache.tags);
var scanner = new Scanner(template);
var start, type, value, chr, token, openSection;
while (!scanner.eos()) {
start = scanner.pos;
value = scanner.scanUntil(openingTagRe);
if (value) {
for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
chr = value.charAt(i);
if (isWhitespace(chr)) {
spaces.push(tokens.length);
} else {
nonSpace = true;
}
tokens.push([ 'text', chr, start, start + 1 ]);
start += 1;
// Check for whitespace on the current line.
if (chr === '\n')
stripSpace();
}
}
// Match the opening tag.
if (!scanner.scan(openingTagRe))
break;
hasTag = true;
// Get the tag type.
type = scanner.scan(tagRe) || 'name';
scanner.scan(whiteRe);
// Get the tag value.
if (type === '=') {
value = scanner.scanUntil(equalsRe);
scanner.scan(equalsRe);
scanner.scanUntil(closingTagRe);
} else if (type === '{') {
value = scanner.scanUntil(closingCurlyRe);
scanner.scan(curlyRe);
scanner.scanUntil(closingTagRe);
type = '&';
} else {
value = scanner.scanUntil(closingTagRe);
}
// Match the closing tag.
if (!scanner.scan(closingTagRe))
throw new Error('Unclosed tag at ' + scanner.pos);
token = [ type, value, start, scanner.pos ];
tokens.push(token);
if (type === '#' || type === '^') {
sections.push(token);
} else if (type === '/') {
// Check section nesting.
openSection = sections.pop();
if (!openSection)
throw new Error('Unopened section "' + value + '" at ' + start);
if (openSection[1] !== value)
throw new Error('Unclosed section "' + openSection[1] + '" at ' + start);
} else if (type === 'name' || type === '{' || type === '&') {
nonSpace = true;
} else if (type === '=') {
// Set the tags for the next time around.
compileTags(value);
}
}
// Make sure there are no open sections when we're done.
openSection = sections.pop();
if (openSection)
throw new Error('Unclosed section "' + openSection[1] + '" at ' + scanner.pos);
return nestTokens(squashTokens(tokens));
}
如上是 parseTemplate 源码,首先会进入 parseTemplate 函数内部,代码依次往下看,我们会看到首先会调用compileTags函数, 该函数有一个参数 tagsToCompile。从源码上下文中可以看到 mustache.tags 默认值为:[ '{{', '}}' ]; 因此 tagsToCompile = [ '{{', '}}' ]; 如果 tagsToCompile 是字符串的话,就执行 tagsToCompile = tagsToCompile.split(spaceRe, 2); 这句代码。
注意:其实我觉得这边 typeof tagsToCompile === 'string' 不可能会是字符串,如果是字符串的话,那么在 parse 函数内部就会直接报错了,如下代码内部:
Writer.prototype.parse = function parse (template, tags) {
var cache = this.cache;
var cacheKey = template + ':' + (tags || mustache.tags).join(':');
}
如上,如果tags 传值了的话,它一定是一个数组,如果是字符串的话,那么使用 join分隔符会报错的。
如果 tagsToCompile 不是一个数组 或 它的长度 不等于2的话,那么就抛出一个错误。因为开始标签和结束标签必须成对传递。
继续往下看代码:openingTagRe = new RegExp(escapeRegExp(tagsToCompile[0]) + '\\s*');
如上代码,首先会调用 escapeRegExp 函数,传递了一个参数 tagsToCompile[0],从上面分析我们知道 tagsToCompile = [ '{{', '}}' ]; 因此 tagsToCompile[0] = '{{'了。escapeRegExp 函数代码如下:
function escapeRegExp (string) {
// $& 的含义是:与 regexp 相匹配的子串。
return string.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
}
因此 代码实际就返回了这样的了
return '{{'.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&');
$& 含义是 与 regexp 相匹配的子串;那么匹配了被替换的结果就是 "\{\{";
因为 它匹配到 "{{", 匹配到第一个 "{" 的话,结果被替换为 "\{", 同理匹配到第二个的时候 也是 '\{'; 因此结果就是:"\{\{"; 也可以理解对 { 进行字符串转义。
因此 openingTagRe = new RegExp("\{\{" + '\\s*') = /\{\{\s*/; 接着往下执行代码:closingTagRe = new RegExp('\\s*' + escapeRegExp(tagsToCompile[1]));
tagsToCompile[1] 值为 "}}"; 同理 escapeRegExp(tagsToCompile[1]) 结果就变为:"\}\}"; 因此 closingTagRe = new RegExp("\\s*" + "\}\}") = /\s*\}\}/;
从上面我们可知:openingTagRe 的含义可以理解为 开始标签,因此正则为 /\{\{\s*/ 就是匹配 开始标签 "{{ " 或 "{{",后面允许0个或多个空白。因为我们编写html模板的时候会这样写 {{ xxx }} 这样的。 因此 openingTagRe = /\{\{\s*/; 同理可知:closingTagRe 就是闭合标签了,因此正则需要为 /\s*\}\}/; 那么可以匹配结束标签 " }}" 或 "}}" 这样的了。因此 closingTagRe = /\s*\}\}/;
继续往下执行代码:
closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + tagsToCompile[1]));
closingCurlyRe = new RegExp('\\s*' + escapeRegExp('}' + "}}")) = /\s*\}\}\}/; 该closingCurlyRe是匹配 " }}}" 或 "}}}" 这样的。
继续往下看代码:var scanner = new Scanner(template);
如上代码,会实列化 Scanner 函数,该函数会传递一个 template参数进去,template参数的值为:"{{#msg}} {{.}} *
{{name}} {{/msg}}"; 下面我们来看下 Scanner 函数源码如下:
function Scanner (string) {
this.string = string;
this.tail = string;
this.pos = 0;
};
因此可以分别得出如下值:
this.string 值为:"{{#msg}} {{.}} * {{name}} {{/msg}}";
this.tail 的值为:"{{#msg}} {{.}} * {{name}} {{/msg}}";
this.pos = 0;
因此 scanner 实例化的值为 = {
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "{{#msg}} {{.}} * {{name}} {{/msg}}",
pos: 0
};
继续看代码:var start, type, value, chr, token, openSection; 这些变量我们先不管他,然后继续代码往下:
然后就进入了while循环代码了,while (!scanner.eos()) {} 这样的。
eos方法如下所示:该方法的作用就是判断 scanner对象的 tail属性值是否等于空,如果等于空,说明模板数据已经被解析完成了。
如果解析完成了,就跳出while循环。如下代码:
Scanner.prototype.eos = function eos () {
return this.tail === '';
};
第一次调用 scanner.eos(); 结果返回 false; 因此进入 while循环内部,start = scanner.pos = 0;
1. 第一次while循环
代码初始化调用 value = scanner.scanUntil(openingTagRe); openingTagRe 值为 /\{\{\s*/; scanUntil函数代码如下:
Scanner.prototype.scanUntil = function scanUntil (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = '';
break;
case 0:
match = '';
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
如上 Scanner.prototype.scanUntil 函数代码可以看到,这里的this指向了 scanner 对象,因此 this.tail = "{{#msg}} {{.}} *
{{name}} {{/msg}}";
re = /\{\{\s*/; 因此 var index = this.tail.search(re) = 0; 会进入 case 0: 的情况,因此 match = ''; 最后 this.pos += ''.length = this.pos + 0 = 0; 最后返回 match = ''; 因此 value = ''; 因此不会进入下面的 if(value){} 的语句里面,
scanner 此时值为:= {
pos: 0,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "{{#msg}} {{.}} * {{name}} {{/msg}}"
}
继续往下代码执行 if (!scanner.scan(openingTagRe)) openingTagRe的值 = /\{\{\s*/; 函数如下:
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;
};
1. if (!scanner.scan(openingTagRe)) {} 调用的时候,openingTagRe 值为 /\{\{\s*/; 因此re的值为 /\{\{\s*/ 此时 this.tail 值为 "{{#msg}} {{.}} *
{{name}} {{/msg}}",re的值为:/\{\{\s*/;
var match = "{{#msg}} {{.}} * {{name}} {{/msg}}".match(/\{\{\s*/); 因此:
match = [
"{{",
index: 0,
groups: undefined,
input: "{{#msg}} {{.}} * {{name}} {{/msg}}"
];
因此 var string = match[0]; 即:string = "{{"; this.tail = this.tail.substring(string.length); this.tail = "{{#msg}} {{.}} *
{{name}} {{/msg}}".substring(2); 最后 this.tail = "#msg}} {{.}} * {{name}} {{/msg}}";
this.pos += "{{".length = 2; 返回 return string; 最后返回 "{{";
此时 scanner 的值为 = {
pos: 2,
tail: "#msg}} {{.}} * {{name}} {{/msg}}",
string: "{{#msg}} {{.}} * {{name}} {{/msg}}"
};
继续代码往下执行,看第二点解释:
2. 在parseTemplate函数中的 type = scanner.scan(tagRe) || 'name'; 这个代码调用的时候; 此时:this.tail的值为 = "#msg}} {{.}} *
{{name}} {{/msg}}"; tagRe 在页面初始化值为 = /#|\^|\/|>|\{|&|=|!/; 因此 re = /#|\^|\/|>|\{|&|=|!/; 因此 var match = this.tail.match(re) = "#msg}} {{.}} * {{name}} {{/msg}}".match(/#|\^|\/|>|\{|&|=|!/);
即match的值为如下:
var match = [
"#",
index: 0,
groups: undefined,
input: "#msg}} {{.}} * {{name}} {{/msg}}"
];
因此 var string = match[0]; 即:string = '#'; this.tail = this.tail.substring(string.length); this.tail = "#msg}} {{.}} *
{{name}} {{/msg}}".substring(1);
最后 this.tail = "msg}} {{.}} * {{name}} {{/msg}}"; this.pos += "#".length = 3; 返回 return string; 最后返回 '#';
最后返回 type 的值为 "#"; 此时的 scanner 的值为:
scanner = {
pos: 3,
tail: "msg}} {{.}} * {{name}} {{/msg}}",
string: "{{#msg}} {{.}} * {{name}} {{/msg}}"
}
代码继续往下执行,看下面第三点解释:
3. 在 parseTemplate函数中的 scanner.scan(whiteRe); 中调用。 whiteRe 在页面初始化的正则为:var whiteRe = /\s*/;
从上面第二次调用的返回结果来看scanner的值为:
scanner的值为:= {
pos: 3,
tail: "msg}} {{.}} * {{name}} {{/msg}}",
string: "{{#msg}} {{.}} * {{name}} {{/msg}}"
};
此时:this.tail 的值为 = "msg}} {{.}} *
{{name}} {{/msg}}"; re = /\s*/;
Scanner.prototype.scan 函数源码如下(方便查看源码):
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;
};
因此 match = "msg}} {{.}} *
{{name}} {{/msg}}".match(/\s*/);
var match = [
"",
index: 0,
group: undefined,
input: "msg}} {{.}} * {{name}} {{/msg}}"
];
因此 var string = match[0]; 即:string = ""; this.tail = this.tail.substring(0) = "msg}} {{.}} *
{{name}} {{/msg}}";
this.pos += 0; this.pos = 3; 返回 return string; 最后返回 "";
此时的 scanner 的值为:
scanner = {
pos: 3,
tail: "msg}} {{.}} * {{name}} {{/msg}}",
string: "{{#msg}} {{.}} * {{name}} {{/msg}}"
};
由上面我们知道 type = "#"; 因此会直接跳到 else 代码内部。
if (type === '=') {
value = scanner.scanUntil(equalsRe);
scanner.scan(equalsRe);
scanner.scanUntil(closingTagRe);
} else if (type === '{') {
value = scanner.scanUntil(closingCurlyRe);
scanner.scan(curlyRe);
scanner.scanUntil(closingTagRe);
type = '&';
} else {
value = scanner.scanUntil(closingTagRe);
}
因此 value = scanner.scanUntil(closingTagRe); 执行,看如下代码解释:
函数代码如下:
Scanner.prototype.scanUntil = function scanUntil (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = '';
break;
case 0:
match = '';
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
scanner.scanUntil(closingTagRe);调用的时候;closingTagRe = "/\s*\}\}/";
因此 re = "/\s*\}\}/"; 从上面分析我们可以知道,最终 scanner 对象返回的值如下:
scanner = {
pos: 3,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "msg}} {{.}} * {{name}} {{/msg}}"
};
因此 此时的 var index = this.tail.search(re) = "msg}} {{.}} *
{{name}} {{/msg}}".search(/\s*\}\}/) = 3;
因此会进入 default 的情况下;match = this.tail.substring(0, index); match = "msg}} {{.}} * {{name}} {{/msg}}".substring(0, 3); = "msg";
因此 this.tail = this.tail.substring(index); this.tail = "msg}} {{.}} * {{name}} {{/msg}}".substring(3);
最后 this.tail 的值为 = "}} {{.}} * {{name}} {{/msg}}"; this.pos += match.length; 因此 this.pos = 3 + 3 = 6;
最后返回 match; 因此最后就返回 "msg" 字符串了。
此时我们再看下 scanner 的值为如下:
scanner = {
pos: 6,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "}} {{.}} * {{name}} {{/msg}}"
};
4. 在 parseTemplate 函数 内部中 if (!scanner.scan(closingTagRe)) 这句代码时候调用。
此时 scanner 的值如下所示:
scanner = {
pos: 6,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "}} {{.}} * {{name}} {{/msg}}"
};
closingTagRe = "/\s*\}\}/";
Scanner.prototype.scan 函数源码如下(方便查看源码):
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;
};
此时 this.tail 的值为 = "}} {{.}} *
{{name}} {{/msg}}"; re = /\s*\}\}/;
var match = "}} {{.}} * {{name}} {{/msg}}".match(/\s*\}\}/);
var match = {
"}}",
groups: undefined,
index: 0,
input: "}} {{.}} * {{name}} {{/msg}}"
};
var string = match[0] = "}}";
this.tail = this.tail.substring(string.length);
因此 this.tail = "}} {{.}} *
{{name}} {{/msg}}".substring(2);
最后 this.tail = " {{.}} * {{name}} {{/msg}}";
this.pos += "}}".length = 8;
最后返回 "}}"; 因此此时的 scannel 的值变为如下:
scanner = {
pos: 8,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: " {{.}} * {{name}} {{/msg}}"
};
代码继续往下执行, 如下代码:
token = [ type, value, start, scanner.pos ];
tokens.push(token);
if (type === '#' || type === '^') {
sections.push(token);
} else if (type === '/') {
// ...
} else if (type === 'name' || type === '{' || type === '&') {
nonSpace = true;
} else if (type === '=') {
// Set the tags for the next time around.
compileTags(value);
}
因此 token = ["#", "msg", 0, 8]; tokens = [["#", "msg", 0, 8]];
因为 type = "#", 因此进入第一个if循环内部。因此 sections = [["#", "msg", 0, 8]];
2. 第二次while循环
此时的 scannel 的值为如下:
scanner = {
pos: 8,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: " {{.}} * {{name}} {{/msg}}"
};
因此 start = 8;
继续执行如下代码:
value = scanner.scanUntil(openingTagRe);
scanUtil 源码函数如下(为了方便理解,继续贴下代码)
Scanner.prototype.scanUntil = function scanUntil (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = '';
break;
case 0:
match = '';
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
openingTagRe的值为:openingTagRe = /\{\{\s*/; 因此 re = /\{\{\s*/; 执行代码:var index = this.tail.search(re), match;
由上返回的数据可知:this.tail = " {{.}} *
{{name}} {{/msg}}"; 因此 var index = " {{.}} * {{name}} {{/msg}}".search(/\{\{\s*/) = 1;
同理进入default语句内部,因此 match = this.tail.substring(0, index);
match = " {{.}} * {{name}} {{/msg}}".substring(0, 1) = " ";
this.tail = this.tail.substring(index);
this.tail = " {{.}} * {{name}} {{/msg}}".substring(1);
最后 this.tail = "{{.}} * {{name}} {{/msg}}";
this.pos += match.length;
this.pos = 8 + 1 = 9;
最后返回 return match; 返回 " ";
因此 此时 scanner 的值变为如下:
scanner = {
pos: 9,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "{{.}} * {{name}} {{/msg}}"
};
执行完成后,value 此时的值为 " "; 因此会进入 if (value) {} 的内部代码。
注意:if("") {} 和 if (" ") {} 结果是不一样的。 "".length = 0; " ".length = 1; 源码如下(方便代码理解):
var regExpTest = RegExp.prototype.test;
function testRegExp (re, string) {
return regExpTest.call(re, string);
}
var nonSpaceRe = /\S/; // 匹配非空白字符
function isWhitespace (string) {
return !testRegExp(nonSpaceRe, string);
}
if (value) {
for (var i = 0, valueLength = value.length; i < valueLength; ++i) {
chr = value.charAt(i);
if (isWhitespace(chr)) {
spaces.push(tokens.length);
} else {
nonSpace = true;
}
tokens.push([ 'text', chr, start, start + 1 ]);
start += 1;
// Check for whitespace on the current line.
if (chr === '\n')
stripSpace();
}
}
因此 chr = ' '; 调用 isWhitespace(chr); 方法,其实就是调用了 RegExp.prototype.test.call(/\S/, ' '); 判断 ' ' 是否是非空白字符,因此返回false,在 isWhitespace 函数内部,使用了 !符号,因此最后返回true。
spaces.push(tokens.length); 从上面代码可知,我们知道 tokens = [["#", "msg", 0, 8]];
因此 spaces = [1]; tokens.push([ 'text', chr, start, start + 1 ]); 执行后 tokens的值变为如下:
tokens = [["#", "msg", 0, 8], ['text', ' ', 8, 9]]; start +=1; 因此 start = 9;
如果 chr === '\n'; 则执行 stripSpace()方法,这里为false,因此不执行。
继续执行如下代码:
if (!scanner.scan(openingTagRe))
break;
openingTagRe的值为:openingTagRe = /\{\{\s*/;
scan 函数代码如下:
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;
};
因此 re = /\{\{\s*/; 从上面可知,我们的scanner的值为如下:
scanner = {
pos: 9,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "{{.}} * {{name}} {{/msg}}"
};
继续执行 Scanner.prototype.scan() 函数内部代码:
var match = this.tail.match(re) = "{{.}} *
{{name}} {{/msg}}".match(/\{\{\s*/);
因此 match的匹配结果如下:
var match = [
"{{",
index: 0,
groups: undefined,
input: "{{.}} * {{name}} {{/msg}}"
];
var string = match[0] = "{{";
this.tail = this.tail.substring(string.length);
因此 this.tail = "{{.}} *
{{name}} {{/msg}}".substring(2);
最后 this.tail = ".}} * {{name}} {{/msg}}";
this.pos += string.length; 因此 this.pos = 9 + 2 = 11;
最后返回 return string; 即返回 "{{";
因此 此时 scanner 的值变为如下:
scanner = {
pos: 11,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: ".}} * {{name}} {{/msg}}"
};
接着继续执行代码:type = scanner.scan(tagRe) || 'name';
tagRe 在页面是定义的正则为:/#|\^|\/|>|\{|&|=|!/;
因此又会执行 Scanner.prototype.scan = function scan (re) {}, 代码如下:
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;
}
由上面可知:
scanner = {
pos: 11,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: ".}} * {{name}} {{/msg}}"
};
re = /#|\^|\/|>|\{|&|=|!/;
因此 var match = this.tail.match(re) = ".}} * {{name}} {{/msg}}".match(/#|\^|\/|>|\{|&|=|!/);
var match = [
">",
index: 10,
groups: undefined,
input: ".}} * {{name}} {{/msg}}"
];
如上代码:match.index === 10; 因此 不等于0;所以就直接返回 ''; 跳出函数,因此 type = 'name' 了;
继续执行如下代码:scanner.scan(whiteRe); whiteRe = /\s*/;
还是一样执行 Scanner.prototype.scan = function scan (re) {} 函数;
因此 var match = ".}} *
{{name}} {{/msg}}".match(/\s*/);
var match = [
'',
groups: undefined,
index: 0,
input: ".}} * {{name}} {{/msg}}"
];
再接着执行代码 var string = match[0] = '';
this.tail = this.tail.substring(0) = ".}} *
{{name}} {{/msg}}";
this.pos += string.length = 11 + 0 = 11;
此时 scanner 的值,和上一步的值一样:
scanner = {
pos: 11,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: ".}} * {{name}} {{/msg}}"
};
最后返回 空字符串 '';
如上我们知道 type = 'name'; 因此 继续进入如下else代码:
if (type === '=') {
} else if (type === '{') {
} else {
value = scanner.scanUntil(closingTagRe);
}
再来看下 scanUntil 代码如下:
Scanner.prototype.scanUntil = function scanUntil (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = '';
break;
case 0:
match = '';
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
如上代码:closingTagRe = /\s*\}\}/;
var index = this.tail.search(re) = ".}} *
{{name}} {{/msg}}".search(/\s*\}\}/) = 1;
因此进入 default语句内部。
因此 match = this.tail.substring(0, index) = ".}} * {{name}} {{/msg}}".substring(0, 1);
match = '.';
this.tail = this.tail.substring(index) = ".}} * {{name}} {{/msg}}".substring(1);
因此 this.tail = "}} * {{name}} {{/msg}}";
this.pos += match.length = 11 + 1 = 12; 最后 return match; 返回 '.';
此时 scanner的值为如下:
scanner = {
pos: 12,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "}} * {{name}} {{/msg}}"
};
接着继续执行 if (!scanner.scan(closingTagRe)){} 代码; closingTagRe = /\s*\}\}/;
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;
};
因此调用 Scanner.prototype.scan() 函数后,
var match = "}} * {{name}} {{/msg}}".match(/\s*\}\}/);
var match = [
"}}",
groups: undefined,
index: 0,
input: "}} * {{name}} {{/msg}}"
];
var string = match[0] = "}}";
this.tail = this.tail.substring(string.length);
因此 this.tail = "}} *
{{name}} {{/msg}}".substring(2);
最后 this.tail = " * {{name}} {{/msg}}";
this.pos = 12 + 2 = 14;
最后 return string; 返回 "}}";
此时 scanner的值为如下:
scanner = {
pos: 14,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: " * {{name}} {{/msg}}"
};
继续执行代码:token = [ type, value, start, scanner.pos ];
因此 token = ['name', '.', 9, 14];
继续往下执行代码:
tokens.push(token);
因此此时 tokens = [
["#", "msg", 0, 8],
['text', ' ', 8, 9],
["name", ".", 9, 14]
];
此时 type = 'name'; 因此 nonSpace = true; 执行完成后。继续while循环。
第三次while循环
此时scanner值为如下:
scanner = {
pos: 14,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: " * {{name}} {{/msg}}"
};
start = scanner.pos; 因此 start = 14;
value = scanner.scanUntil(openingTagRe); 执行这句代码:
openingTagRe = /\{\{\s*/;
Scanner.prototype.scanUntil = function scanUntil (re) {
var index = this.tail.search(re), match;
switch (index) {
case -1:
match = this.tail;
this.tail = '';
break;
case 0:
match = '';
break;
default:
match = this.tail.substring(0, index);
this.tail = this.tail.substring(index);
}
this.pos += match.length;
return match;
};
var index = this.tail.search(re) = " *
{{name}} {{/msg}}".search(/\{\{\s*/);
因此 var index = 8;
然后又继续进入 default语句;此时 match = this.tail.substring(0, index);
match = " * {{name}} {{/msg}}".substring(0, 8) = " * ";
this.tail = this.tail.substring(index) = " * {{name}} {{/msg}}".substring(8);
因此 this.tail = "{{name}} {{/msg}}";
this.pos += match.length = 14 + 8 = 22;
因此 此时scanner值为如下:
scanner = {
pos: 22,
string: "{{#msg}} {{.}} * {{name}} {{/msg}}",
tail: "{{name}}
{{/msg}}"
};
最后返回 " *
因此继续进入 if (value) {} 代码内部:
if (value) { for (var i = 0, valueLength = value.length; i < valueLength; ++i) { chr = value.charAt(i); if (isWhitespace(chr)) { spaces.push(tokens.length); } else { nonSpace = true; } tokens.push([ 'text', chr, start, start + 1 ]); start += 1; // Check for whitespace on the current line. if (chr === '\n') stripSpace(); } } var regExpTest = RegExp.prototype.test; function testRegExp (re, string) { return regExpTest.call(re, string); } var nonSpaceRe = /\S/; function isWhitespace (string) { return !testRegExp(nonSpaceRe, string); }
而此时 value.length = 8了;因此在for语句需要循环8次。 i = 0:chr = value.charAt(i) = " *".charAt(0) = " "; 执行 isWhitespace(chr); 函数代码,如下代码: RegExp.prototype.test.call(/\S/, ' '); 判断 ' ' 是否是非空白字符,因此返回false,因此 !false 就是true了。 因此 执行 spaces.push(tokens.length); 之前tokens = [["#", "msg", 0, 8],["text", " ", 8, 9],["name", ".", 9, 14]]; spaces = [1]; 因此此时 spaces = [1, 3]; 了。 接着执行 tokens.push([ 'text', chr, start, start + 1 ]); 因此 tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ]; start += 1; 因此 start = 15; i = 1:chr = value.charAt(i) = " *{{/msg}}".match(/\{\{\s*/);".charAt(1) = "*"; 执行 isWhitespace(chr); 返回false; 因此进入else 语句; 此时:nonSpace = true; 继续执行代码:tokens.push([ 'text', chr, start, start + 1 ]); 因此tokens的值变为 如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16] ]; start += 1; 因此 start = 16; i = 2:chr = value.charAt(i) = " *".charAt(2) = " "; 执行 isWhitespace(chr); 返回true; 和第一步一样, 因此 执行 spaces.push(tokens.length); 因此 spaces.push(tokens.length); 即 spaces = [1, 3, 5]; 继续执行代码:tokens.push([ 'text', chr, start, start + 1 ]); 因此tokens的值变为如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17] ]; start +=1; 因此 start = 17; i = 3: chr = value.charAt(i) = " *".charAt(3) = "<"; 执行 isWhitespace(chr); 返回false, 因此进入else 语句。此时:nonSpace = true; 继续执行代码:tokens.push([ 'text', chr, start, start + 1 ]); 因此tokens的值变为 如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18] ]; start +=1; 因此 start = 18; i = 4: 同理,和第三步一样。因此 chr = 'd'; 因此tokens的值变为 如下: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19] ]; start +=1; 因此 start = 19; i = 5; 同理,和第三步一样。因此 chr = 'i'; 最后tokens的值变为: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20] ]; start +=1; 因此 start = 20; i = 6; 同理,和第三步一样。因此 chr = 'v'; 最后tokens的值变为: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ]; start +=1; 因此 start = 21; i = 7; 同理,和第三步一样。因此 chr = '>'; 最后tokens的值变为: tokens = [ ["#", "msg", 0, 8], ["text", " ", 8, 9], ["name", ".", 9, 14], ["text", " ", 14, 15], ["text", "*", 15, 16], ["text", " ", 16, 17], ["text", "<", 17, 18], ["text", "d", 18, 19], ["text", "i", 19, 20], ["text", "v", 20, 21], ["text", ">", 21, 22] ]; start +=1; 因此 start = 22; ng: "{{#msg}} {{.}} *{{/msg}}" }{{name}}{{/msg}}", tail: "{{name}}继续执行代码:if (!scanner.scan(openingTagRe)) {}; 因此进入 Scanner.prototype.scan = function scan (re) {} 函数代码内部。源码如下:
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; };re 值 = /\{\{\s*/; 此时 scanner 值为如下:
{{/msg}}" }scanner = { pos: 22, string: "{{#msg}} {{.}} *{{name}}{{/msg}}", tail: "{{name}}因此 var match = "{{name}}
{{/msg}}" ];var match = [ "{{", index: 0, groups: undefined, input: "{{name}}
var string = match[0]; 因此 var string = "{{";
this.tail = this.tail.substring(string.length) = "{{name}}
因此:this.tail = "name}}