2009 年 10 月,Chris Wanstrath 发布了 Mustache,它是一个 Ruby 工具类库。不久后,Jan Lehnardt 便发布了 Mustache.js。
Mustache 的设计哲学是 logic-less template,即不用编写逻辑语句的模板系统,即没有 if 语句、else 从句以及循环等逻辑语句,这意味着它的语法相对简单,从某种意义上来说不容易出错。
Mustache 的立场是让使用者来适应它,而不是它来满足使用者遇到的各种场景。到目前为止,Mustache 有数十种编程语言的实现,Mustache.js 是 Mustache 模板系统的 JavaScript 实现。本文只讲解 Mustache.js,版本如下:
Mustache.js 主要支持以下 5 种语法:
-
{{name}}
变量
-
{{#name}}...{{/name}}
区块
-
{{^name}}...{{/name}}
反向区块
-
{{!name}}
注释
-
{{>name}}
局部模板
用两对大括号(大括号顺时针转 90 度后就是 Mustache 的 logo)括起来的标记({{name}}
)叫做 Mustache 标签,大括号中的内容(name
)叫做该标签的键(key
)。
变量 {{name}}
- 如果
name
是简单变量,它会替换为当前 context 对象上的 name
属性的值。如果 name
属性未定义或者 name
的值为 null
或者 undefined
,则替换为空字符串,否则就调用 name
值的 toString()
方法作为替换的值。
- 如果
name
是函数,则使用函数的返回值,然后再按上面的规则取值。函数调用中的上下文对象 this
为 view
对象。
其实确切地说 this
指向的是所在层级的父层对象(区块运算符 '#'
运算符见下小节)。
但像下面这么写,this
又是指向 view
(运算符 '.'
见下小点的内容),这是对 '.'
运算符作特殊处理时导致的前后不一致的情况,这是个问题。
-
name
除了不能出现 .
字符外,只要可以作为对象的键名都是有效的,包括中间带空格的变量和 JavaScript 关键字以及保留字等等。
-
'.'
运算符有特殊的作用,和 js 的语法一样,取对象的值。只支持 '.'
取对象的值,不支持 '[]'
取值运算符。
- 默认所有的变量都会转义 html 字符,如果不想转义 html 字符,则可以使用
{{{name}}}
或者 {{&name}}
。
区块 {{#name}}...{{/name}}
- 如果区块的键不存在,或者键的值是
null
、undefined
、false
、0
、NaN
、空字符串或者空数组,则区块不会渲染。注意,如果是空对象 {}
,是会渲染的。
- 否则,会渲染区块的内容。如果键的值是数组,则会渲染多次区块内容,次数为数组的长度。
如果是字符串数组,则可以使用 .
代表当前迭代的字符串值。
如果区块中的键的值是函数,则会调用该函数,函数中的 this
为当前所迭代的对象。
- 如果区块的键是函数,则会调用该函数。函数的第一个参数是区块的内容块(未渲染)。第二个参数是一个特殊的渲染函数,它的
view
参数是当前的 view
,调用的 context
对象即为当前的 view
对象。
反向区块 {{^name}}...{{/name}}
- 反向区块,顾名思义,情况和区块正相反,即区块会渲染的话,则反向区块就不渲染。如果区块的键是函数,则依据函数的返回值进行判断。
注释 {{!name}}
局部模板 {{>name}}
- 局部模板在运行期渲染(而不是编译期),所以局部模板可以递归使用,但要避免产生无限循环。将局部模板传给
Mustache.render
方法的第三个参数即可。
下面的例子会产生无限循环。因为 Mustache.js 发现当前对象没有 children
属性时,它会往上查找,这和 JavaScript 中查找变量的过程是类似的:在当前作用域中找不到变量,就到父级作用域中查找。
设置自定义标签(Set Delimiter)
- Mustache.js 的标签是两对大括号,但如果想输出两对大括号就比较麻烦(可以使用 html 标签把单个大括号包起来,但在实际情况中这么做就太麻烦了)。Mustache.js 可以设置自定义标签,不使用默认的
{{}}
。
Mustache.js 把标签存放在 tags
属性中,可以通过更改 Mustache.tags
的值,则会更换默认标签,注意,这是全局范围内的更换,所以不要轻易这么做。
预解析和缓存模板
默认情况下,当 Mustache.js 首次解析了某个模板后,它会把完整的标记树(token tree,这个涉及到 Mustache.js 的实现)缓存起来。之后如果又遇到相同的模板,就会绕过解析步骤直接使用缓存模板,这样渲染就快得多了。可以预先使用 Mustache.parse
方法。
API
Mustache.js 暴露的 API 主要就是一个,即 Mustache.render
方法。还有一个 Mustache.to_html
方法,这个方法是为了兼容 0.4.x
版本的,所以已经不推荐使用这个方法,官方文档甚至都没提到这个方法。
问题
在使用 Mustache.js 的过程中,遇到比较多的一个问题是无法遍历对象,即需要同时使用对象的 key
和 value
。这个问题 Mustache.js 没提供解决方案,只能预先对对象进行处理,所以说,Mustache.js 的立场是让使用者来适应它,而不是它来满足使用者遇到的各种场景。
结语
2011 年 2 月,Jan Lehnardt 写了一篇博客,展望了 Mustache 2.0 和 Mustache 的未来。他例举了在使用 Mustache 过程中遇到的一些问题(很多 idea 都是来自于后起之秀 Handlebars.js),并打算积极推动 Mustache 的社区建设并推动大家起草 Mustache 2.0 规范,然后所有语言都实现新的规范。
2013 年 11 月,Jan Lehnardt 又写了一篇博客,介绍了 Mustache.js 的一些背景知识。我想他有点无奈,因为两年前的愿景进展缓慢,Mustache 2.0 更是遥遥无期。主要是 Mustache 的作者们难以达成共识,我想他们是不想破坏 Mustache 的设计哲学,即不想把 logic-less template 变成 logic template。于是,Jan Lehnardt 又把他的精力放在其他事情上面了。
对于 Mustache 作者们的坚持,是对的还是错的?我想每个人都有自己的看法。世界就是如此的奇妙,坚持的路上,有失去的机会,也有不错的回报。
在刚开始使用 Mustache.js 时,有时很纳闷怎么这么简单的功能也不提供呢?随着对 Mustache 的深入了解,我现在挺欣赏 Mustache 作者们的坚持。
留下评论