Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Underscore 模板引擎 API 更新 #4

Open
cssmagic opened this issue Jun 16, 2013 · 9 comments
Open

Underscore 模板引擎 API 更新 #4

cssmagic opened this issue Jun 16, 2013 · 9 comments

Comments

@cssmagic
Copy link
Owner

cssmagic commented Jun 16, 2013

Underscore 模板引擎 API 更新

语法

Underscore 的模板引擎 _.template() 脱胎于 jQuery 作者的作品 Micro-Templating。但从 Underscore 1.3.3 开始,这个方法做了较大的调整,在保留旧语法的基础上,还新增支持了一个 {variable: 'foo'} 对象作为第三个参数。

一旦传入了这个参数,则模板(第一个参数)中的变量将不再指向待渲染数据(第二个参数)的属性,模板中的 foo 变量将直接指向待渲染数据(第二个参数)自身

比如,原来的调用方法:

_.template('I love <%= person %>.', {person: 'you'});

在新语法下可以写为:

_.template('I love <%= foo.person %>.', {person: 'you'}, {variable: 'foo'});

比较

看起来似乎变复杂了,但这样做有两个好处:

内部实现

旧语法在实现上,需要使用 with 声明来实现对数据对象属性的查找。with 有哪些问题,这里就不多说了。而新语法由于已经在模板中指定了数据对象自身,则不需要用 with 来搜索其属性。据官方文档称,“模板渲染性能得到极大提升”。

外部接口

新语法带来了一个隐性的改良——接口灵活性更高,即待渲染数据(第二个参数)可以不仅是对象,也可以是数组等其它数据类型。仍然以上面的代码为例,用新语法还可以写成这样:

_.template('I love <%= foo %>.', 'you', {variable: 'foo'});

这样传入的数据更自由,数组等数据不需要被包装为对象再传入了。

使用

在实际应用中,往往存在某个常用的模板需要被多次渲染的情况。此时,为优化性能,我们通常会采用“两步渲染法”——先把模板编译成模板函数备用;按需执行已经编译好的模板函数,把不同的数据渲染为不同的结果——以避免同一模板的重复编译。如下所示:

var fnRender = _.template('I love <%= person %>.');
fnRender({person: 'you'});  //'I love you.'
fnRender({person: 'her'});  //'I love her.'

在这种情况下,我们无法使用 {variable: 'foo'} 参数。那怎么办呢?

幸好有 _.templateSettings 可以进行 _.template() 的全局设置:

_.extend(_.templateSettings, {variable: 'foo'});

在此之后编译的所有模板函数即工作在新语法之下。需要注意的是,这个设置是全局的,也就是说,当前页面的所有模板和相关功能都需要以新语法来写,并将 foo 统一命名。


更新

从 Underscore 1.7 开始,这个 API 的行为又发生了一些变化,我们有必要再来看一看。

从这个版本开始,_.template() 方法将不再接受模板数据了,它的返回值就总是编译生成的模板函数了。也就是说原先一步渲染模板的用法需要修改成两步走:

// before
_.template('I love <%= person %>.', {person: 'you'});

// after
_.template('I love <%= person %>.')({person: 'you'});

看起来很蛋疼?无关痛痒?其实 Underscore 下决心引入这个 “破坏性变更” 还是很有深意的。我认为这个改动的好处在于:

  • 消灭了返回值的不确定性,令这个 API 的行为更易于理解。
  • 去掉一步到位的用法,虽然牺牲了眼前的便利,但同时也强制使用者了解模板引擎的基本原理,一定程度上会推动使用者考虑模板缓存,进而提升应用的整体性能。

© Creative Commons BY-NC-ND 4.0   |   我要订阅   |   我要打赏

@edokeh
Copy link

edokeh commented Jun 16, 2013

汗,1.4 都用了好久了,还真没注意到有这个更新,这么个设置还是挺有用的
主要是项目里全换成 handlebars 了,underscore 的模板还是有点弱啊

@cssmagic
Copy link
Owner Author

@edokeh
我的都是小项目,本来就依赖 Underscore,模板引擎也直接用它的了。简单够用,更复杂的估计我也不会用,呵呵。
不过等等,Underscore 的模板支持完整的 JS 逻辑,开放、灵活,可以说它简单,但并不“弱”吧?

@edokeh
Copy link

edokeh commented Jun 17, 2013

嘿嘿,我们也是小项目啊,只是碰到些奇怪的需求嘛
主要遇到的问题是,underscore 缺少一个 helper 层,导致某些功能实现起来很别扭
我们的前端模板里面某些文字要做 i18n ,用 underscore 的话就只能污染数据对象来实现了

比如模板是这样

<div>
  <h1><%= i18n('title') %>:<%= name %></h1>
</div>

那么就得这么弄

var data = {name:'edokeh'};

_.extend(data, {
  i18n: function(key) {
    return I18N_DATA[key]
  }
})

_.template('...', data);

这时候 data 对象就被污染了,可能会有些潜在问题,而且从逻辑上来说也很不好啊

@cssmagic
Copy link
Owner Author

@edokeh
Helper 层完全可以自己写,我感觉反而更灵活(借口,实际上模板规则太多了我记不住啊,哈哈)。Underscore 模板内部可以调用任何全局作用域可以调用的函数和变量,因为模板内的所有逻辑代码就是原生的 JavaScript 代码。

比如你的例子,其实不需要扩展数据源,直接这样写模板就可以:

<div>
  <h1><%= I18N_DATA['title'] %>:<%= name %></h1>
</div>

如果一定解除模板对全局变量的依赖,并且不想改写数据源,也可以为模板单独准备数据:

var data = {name: 'edokeh'};
var dataToRender = _.extend({}, data, {
  title: I18N_DATA['title']
});

然后使用这个模板:

<div>
  <h1><%= title %>:<%= name %></h1>
</div>

当然这样稍显琐碎,还是前一种方法更直观。我感觉模板里存在适度逻辑(或对全局变量的适度调用)还是 OK 的。

@edokeh
Copy link

edokeh commented Jun 17, 2013

我们用 SeaJS 封装的模块,没有全局变量。。。
那个扩展的方式其实我们之前采用过的,可是写起来真的好麻烦。。。索性整体换个模板系统好了
其实 handlebars 用起来也不是很爽,里面完全不能放逻辑,不过幸好我们的系统没那么复杂,基本都能应对
赞同你说的适度逻辑的观点,handlebars 的世界观有些过于激进了

@cssmagic
Copy link
Owner Author

@edokeh 是这样啊,明白了。
p.s. 不过 Sea.js 模块应该也可以手工暴露全局变量的,嘿嘿。

@godpeacy
Copy link

是否有哪个线上应用用到了这个!

@cssmagic
Copy link
Owner Author

@godpeacy 不好意思现在才看到你的留言。

我自己的经验是这样的:


更新:

@Ruy-Zhou
Copy link

underscore is a greate template engine for html render.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants