☚ GZOOL Quality Without A Name

Mustache.js 使用简介

Mustache.js

2009 年 10 月,Chris Wanstrath 发布了 Mustache,它是一个 Ruby 工具类库。不久后,Jan Lehnardt 便发布了 Mustache.js

Mustache 的设计哲学是 logic-less template,即不用编写逻辑语句的模板系统,即没有 if 语句、else 从句以及循环等逻辑语句,这意味着它的语法相对简单,从某种意义上来说不容易出错。

Mustache 的立场是让使用者来适应它,而不是它来满足使用者遇到的各种场景。到目前为止,Mustache 有数十种编程语言的实现,Mustache.js 是 Mustache 模板系统的 JavaScript 实现。本文只讲解 Mustache.js,版本如下:

console.log(Mustache.version) // 0.8.1

Mustache.js 主要支持以下 5 种语法:

用两对大括号(大括号顺时针转 90 度后就是 Mustache 的 logo)括起来的标记({{name}})叫做 Mustache 标签,大括号中的内容(name)叫做该标签的键(key)。

变量 {{name}}

var template = '{{name}}'

// {} 没有 `name` 属性,结果是空字符串
Mustache.render(template, {})

Mustache.render(template, {name: null}) // ''
Mustache.render(template, {name: undefined}) // ''

// false.toString() === 'false'
Mustache.render(template, {name: false})

// {}.toString() === '[object Object]'
Mustache.render(template, {name: {}})

// [].toString() === ''
Mustache.render(template, {name: []})
var template = '{{name}}'

var view = {
    name: function () {
        console.log(this === view) // true
        return 'Don Quixote'
    }
}

Mustache.render(template, view) // 'Don Quixote'

其实确切地说 this 指向的是所在层级的父层对象(区块运算符 '#' 运算符见下小节)。

var template = '' +
    '{{#user}}' +
    '   {{#hasName}}' +
    '   <strong>{{user.name}}</strong>' +
    '   {{/hasName}}' +
    '{{/user}}'

var view = {
    user: {
        hasName: function () {
            console.log(this === view.user) // true
            return true
        }
    }
}

Mustache.render(template, view)

但像下面这么写,this 又是指向 view (运算符 '.' 见下小点的内容),这是对 '.' 运算符作特殊处理时导致的前后不一致的情况,这是个问题。

var template = '' +
    '{{#user.hasName}}' +
    '   <strong>{{user.name}}</strong>' +
    '{{/user.hasName}}'

var view = {
    user: {
        hasName: function () {
            console.log(this === view) // true
            return true
        }
    }
}

Mustache.render(template, view)
var template = '{{my name is}}'

var view = {
    'my name is': 'Don Quixote'
}

Mustache.render(template, view) // 'Don Quixote'
var template = '{{name.lastName}} {{name.firstName}}'

var view = {
    name: {
        firstName: 'Quixote',
        lastName: 'Don'
    }
}

Mustache.render(template, view) // 'Don Quixote'

template = '{{name.lastName}}'

view = {
    'name.lastName': 'Don'
}

Mustache.render(template, view) // ''

template = '{{name["lastName"]}}'

view = {
    name: {
        lastName: 'Don'
    }
}

Mustache.render(template, view) // ''
var view = {
    'title': '<b>Knight</b>'
}

Mustache.render('{{title}}', view) // &lt;b&gt;Knight&lt;&#x2F;b&gt;
Mustache.render('{{&title}}', view) // <b>Knight</b>
Mustache.render('{{{title}}}', view) // <b>Knight</b>

区块 {{#name}}...{{/name}}

var template = '' +
    '{{{#name}}' +
    'Never shown!' +
    '{{{/name}}'

var view = {
    'name': {}
}

Mustache.render(template, view) // ''

view = {
    'name': []
}

Mustache.render(template, view) // 'Never shown!'
var template = '' +
    '{{#stooges}}' +
    '<b>{{name}}</b>' +
    '{{{/stooges}}'

var view = {
    'stooges': [
        {'name': 'Moe'},
        {'name': 'Larry'},
        {'name': 'Curly'}
    ]
}

Mustache.render(template, view) // <b>Moe</b><b>Larry</b><b>Curly</b>

如果是字符串数组,则可以使用 . 代表当前迭代的字符串值。

var template = '' +
    '{{#stooges}}' +
    '<b>{{.}}</b>' +
    '{{{/stooges}}'

var view = {
    'stooges': ['Moe', 'Larry','Curly']
}

Mustache.render(template, view) // <b>Moe</b><b>Larry</b><b>Curly</b>

如果区块中的键的值是函数,则会调用该函数,函数中的 this 为当前所迭代的对象。

var template = '' +
    '{{#names}}' +
    '<b>{{name}}</b>' +
    '{{/names}}'

var view = {
    names: [
        {'firstName': 'Don', 'lastName': 'Quixote'}
    ],
    name: function () {
        return this.firstName + ' ' + this.lastName
    }
}

Mustache.render(template, view) // <b>Don Quixote</b>
var template = '' +
        '{{#bold}}' +
        'Hi {{name}}.' +
        '{{/bold}}'

var view = {
    'name': 'Tater',
    'bold': function () {
        return function (text, render) {
            return '<b>' + render(text) + '</b>'
        }
    }
}

Mustache.render(template, view) // <b>Hi Tater.</b>

反向区块 {{^name}}...{{/name}}

var template = '' +
    '{{^name}}' +
    'Shown!' +
    '{{/name}}'

var view = {
    'name': []
}

Mustache.render(template, view) // 'Shown!'

注释 {{!name}}

var template = '<h1>Today{{! ignore me}}.</h1>'

Mustache.render(template) // <h1>Today.</h1>

局部模板 {{>name}}

var template = '' +
    '{{#names}}' +
    '{{> user}}' +
    '{{/names}}'

var userTemplate = '<strong>{{name}}</strong>'

var view = {
    names: [
        {
            name: 'Don Quixote'
        }
    ]
}

// <strong>Don Quixote</strong>
Mustache.render(template, view, {
    user: userTemplate
})

下面的例子会产生无限循环。因为 Mustache.js 发现当前对象没有 children 属性时,它会往上查找,这和 JavaScript 中查找变量的过程是类似的:在当前作用域中找不到变量,就到父级作用域中查找。

var view = {
    title: 'title1',
    children: [
        {
            title: 'title1-1'
        },
        {
            title: 'title1-2',
            children: [
                {
                    title: 'title1-2-1'
                }
            ]
        }
    ]
}

var template = '' +
    '{{title}}' +
    '<ul>' +
    '{{#children}}' +
    '   <li>{{>panel}}</li>' +
    '{{/children}}' +
    '</ul>'

var partials = {panel: template}

// Uncaught RangeError: Maximum call stack size exceeded
Mustache.render(template, view, partials)

设置自定义标签(Set Delimiter)

var template = '' +
    '{{ firstName }}' +
    '{{=<% %>=}}' +
    '<% lastName %>' +
    '<%={{ }}=%>' +
    ' is {{ title }}'

var view = {
    firstName: 'Don',
    lastName: 'Quixote',
    title: 'Knight'
}

Mustache.render(template, view) // Don Quixote is Knight

Mustache.js 把标签存放在 tags 属性中,可以通过更改 Mustache.tags 的值,则会更换默认标签,注意,这是全局范围内的更换,所以不要轻易这么做。

Mustache.tags = ['<%', '%>']

var template = '<% name %>'

var view = {
    name: 'Don Quixote'
}

Mustache.render(template, view)

预解析和缓存模板

默认情况下,当 Mustache.js 首次解析了某个模板后,它会把完整的标记树(token tree,这个涉及到 Mustache.js 的实现)缓存起来。之后如果又遇到相同的模板,就会绕过解析步骤直接使用缓存模板,这样渲染就快得多了。可以预先使用 Mustache.parse 方法。

Mustache.parse(template);

// Then, sometime later.
Mustache.render(template, view);

API

Mustache.js 暴露的 API 主要就是一个,即 Mustache.render 方法。还有一个 Mustache.to_html 方法,这个方法是为了兼容 0.4.x 版本的,所以已经不推荐使用这个方法,官方文档甚至都没提到这个方法。

问题

在使用 Mustache.js 的过程中,遇到比较多的一个问题是无法遍历对象,即需要同时使用对象的 keyvalue。这个问题 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 作者们的坚持。

comments powered by Disqus