课程介绍
Mustache是基于JavaScript实现的模版引擎,类似于JQuery Template,但是这个模版更加的轻量级,语法更加的简单易用,很容易上手。
mustache.js 是 mustache 模板系统的JavaScript实现。Mustache 是一套轻逻辑的模板语法。它可以用来处理 HTML 、配置文件、源代码等任何文件。它把模板中的标签展开成给定的数据映射或者对象中的属性值。我们之所以说“轻逻辑”,是因为模板里面没有if语句、else语句或者for循环。只有模板标签。
下面是一个示例:
```
<script id="template" type="x-tmpl-mustache">
<div>
{{#name}}
<span>is {{age}} years old</span>
{{/name}}
</div>
</script>
const view = {
name: 'Zuckjet',
gender: 25
};
const html = document.getElementById('template').innerHTML;
const output = Mustache.render(html, view);
console.log(output);
```
上述代码输出结果为:
```
<div>
<span>Zuckjet is 25 years old</span>
</div>
```
我们可以看到,通过使用Mustache.render方法就能把给定的模板转变为我们需要的内容,那么mustache.js是如何实现这些功能的呢?
原理介绍
mustache.js主要入口函数如下:
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, tags);
};
通过上面的代码,我们可以很清楚地知道mustache.js渲染模板主要分为两大步骤:
- 将模板转换为tokens数组
- 将tokens数组转换为对应的html
拿上面基本使用章节中的例子来说,Mustache.render方法返回的结果就是我们需要的内容字符串,这一点没有什么疑问。但是对于tokens数组是什么样子我们可能会比较好奇,现在我们来看看基本示例中生成的tokens数组:
tokens = [
["text", "↵ <div>↵", 0, 11],
["#", "name", 17, 26, Array(5), 82],
["text", " </div>↵ ", 92, 105]
]
其中元素tokens[1]数组的内容为:
tokens[1] = [
["text", " <span>", 27, 39],
["name", "name", 39, 47],
["text", " is ", 47, 51],
["name", "age", 51, 58],
["text", " years old</span>↵", 58, 76]
]
不难发现tokens数组每一项都是形如:[ type, value, start, end ]
这种形式,分别表示节点类型、节点值、节点匹配的起始位置和结束位置。
现在我们继续看看mustache.js是如何生成这些tokens数组的。第一步生成tokens数组中使用的this.parse(template, tags);
方法内部主要调用的是 parseTemplate (template, tags)
函数:
function parseTemplate(template, tags){
// 列出该函数内部几个重要的方法,其余代码省略
scanner.scanUntil(regx);
scanner.scan(regx);
squashTokens(tokens);
nestTokens(squashedTokens);
}
Scanner类是mustache.js中一个核心的工具类,主要功能是根据传入的正则表达式切割字符串。其中scanner.scanUntil(regx)
是把符合正则表达式之前的字符串截取出来,sanner.scan(regx)
是把符合正则表达式内容截取出来。我们看看这两个函数的实现,就容易发现区别了:
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.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;
};
接下来我们看看squashTokens这个函数,squash这个单词我们并不陌生,git里面可以用这个命令合并多个commit记录。因此squashTokens的作用也不难猜到,主要用来合并text token。
tokens = [
["text","<", 5, 6]
["text","d", 6, 7]
["text","i", 7, 8]
["text","v", 8, 9]
["text",">", 9, 10]
];
squashTokens = [["text","<div>", 5, 10]];
nestTokens的作用则是把squashTokens数组转化成层级嵌套的树结构,也就是本章节开头列出的tokens数组结构。
至此如何生成tokens数组我们已经弄清楚了,接下来的任务主要是了解如何由tokens数组生成我们需要的html内容。这一步的调用的this.renderTokens
方法组成代码如下:
Writer.prototype.renderTokens = function renderTokens (tokens, context, partials, originalTemplate, tags) {
var buffer = '';
var token, symbol, value;
for (var i = 0, numTokens = tokens.length; i < numTokens; ++i) {
value = undefined;
token = tokens[i];
symbol = token[0];
if (symbol === '#') value = this.renderSection(token, context, partials, originalTemplate);
else if (symbol === '^') value = this.renderInverted(token, context, partials, originalTemplate);
else if (symbol === '>') value = this.renderPartial(token, context, partials, tags);
else if (symbol === '&') value = this.unescapedValue(token, context);
else if (symbol === 'name') value = this.escapedValue(token, context);
else if (symbol === 'text') value = this.rawValue(token);
if (value !== undefined)
buffer += value;
}
return buffer;
};
这个方法里重点是几个if else循环的处理,根据不同的token类型进行不同的处理。比如symbol等于text的时候,直接取出字符串值。symbol等于#的时候,会判断值的真假,为假时就不返回空。最后把各个类型的token处理的结果拼接起来,就是我们需要的html内容了。
总结
我们知道,简单的模板引擎可以使用正则等字符串处理手段将模板字符串拼接为js源码字符串,然后使用eval或者new Function()手段执行拼接后的源码。这种方式实现模板引擎缺陷也很明显,不论是eval还是new Function()执行拼接后的js代码,其效率都很低下,其次动态执行字符串对于调试也很不友好。
mustache.js相当于是实现了自己的一套语法逻辑。通过构造tokens数组树,然后根据tokens数组树解析成目标字符串。不过自己实现语法规则,肯定无法实现js全部语言特性,因此mustache.js只能通过自己的语法约定,通过特定方式实现少数的js语法特性。