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 _.template 方法使用详解 #26

Open
lessfish opened this issue Oct 31, 2016 · 3 comments
Open

Underscore _.template 方法使用详解 #26

lessfish opened this issue Oct 31, 2016 · 3 comments

Comments

@lessfish
Copy link
Owner

前文 浅谈 Web 中前后端模板引擎的使用 我们简单了解了模板引擎在前后端的应用场景,本文重点深入 Underscore 的模板函数 _.template,来看看它的用法以及实现原理。

from simplest

我们从 官方文档 中最简单的例子说起。

var compiled = _.template("hello: <%= name %>");
var html = compiled({name: 'moe'}); // hello: moe

{name: 'moe'} 模拟后台请求到的接口数据,而变量 html 则为拼接成的字符串,之后便可以用 innerHTML 方法加入到页面生成 DOM。

这一切是如何做到的?我们可以打印看下 compliled 方法是个什么样子(需要去 Underscore 源码中打印)。

大概是这个样子(其实不完全准确,真实的应该还会有个 _ 参数传入,使得函数能用 Underscore 内部方法):

function(obj){
var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};
with(obj||{}){
__p+='hello: '+
((__t=( name ))==null?'':__t)+
'';
}
return __p;
}

仔细想想,其实就是对模板字符串进行了正则解析,将需要填入数据的位置预留出来,拼接成一个字符串,用 new Function 构造一个方法(动态执行 JavaScript 字符串),方法中有大量的字符串拼接过程,然后将数据代入这个方法,返回我们需要的 HTML 字符串。

盗用 木神 两张图,过程非常清晰。

1

2

三种模板

_.template 支持以下三种模板。

1. <%  %> - to execute some code
2. <%= %> - to print some value in template
3. <%- %> - to print some values HTML escaped

<% %> 里包裹的是一些可执行的 JavaScript 语句,比如 if-else 语句,for 循环语句,等等。<%= %> 正是我们前面使用的,会打印传入数据相应的 key 的值,<%- %> 和前者相比,多了步 HTML 实体编码的过程,可以有效防止 XSS 攻击。

举个栗子:

<div></div>
<script src="underscore.js"></script>
<script type="text/template" id="tpl">
  <ul class="list">
    <% _.each(obj, function(e, i, a){ %>
      <% if (i === 0) %>
        <li><%- e.name %>
      <% else if (i === a.length - 1) %>
        <li class="last-item"><%= e.name %></li>
      <% else %>
        <li><%= e.name %></li>
    <% }) %>
  </ul>
</script>
<script>
// mock data
var data = [{name: "<script>"}, {name: "orange"}, {name: "peach"}];

var compiled = _.template(document.getElementById("tpl").innerHTML);
var html = compiled(data);
// console.log(html)
document.querySelector("div").innerHTML = html;
</script>

将数据用 li 标签循环展示,并且将第一个值实体编码了。

其他功能

_.template 最基础的应用就是这样。

如果你不喜欢它默认的模板风格,也可以自己定义,注意 key 必须和源码中的 key 保持一致,才能覆盖。

_.templateSettings = {
  // 三种渲染模板
  evaluate    : /<%([\s\S]+?)%>/g,
  interpolate : /<%=([\s\S]+?)%>/g,
  escape      : /<%-([\s\S]+?)%>/g
};

有两种方式,一种是直接修改 _.templateSettings 变量(不推荐,修改了源码中的变量)

_.templateSettings = {
  interpolate: /\{\{(.+?)\}\}/g 
};

var template = _.template("Hello {{ name }}!");
var ans = template({name: "Mustache"});
console.log(ans); // Hello Mustache!

比较好的方法是作为 _.template 的第二个参数 settings 传入:

var settings = {
  interpolate: /\{\{(.+?)\}\}/g	 // 覆盖 _.templateSettings.interpolate
};

var template = _.template("Hello {{ name }}!", settings);
var ans = template({name: "Mustache"});
console.log(ans); // Hello Mustache!

我们还能设定 settings.variable 指定 scope:

var template = _.template("Using 'with': <%= data.answer %>", {variable: 'data'})
var ans = template({answer: 'no'});
console.log(ans)  // Using 'with': no

预编译

模板引擎一般都带有预编译功能,_.template 也不例外。

什么是预编译?有什么用?

上面的代码有两个痛点:

  1. 性能:模板引擎渲染的时候依赖 Function 构造器实现,Function 与 eval、setTimeout、setInterval 一样,提供了使用文本访问 javascript 解析引擎的方法,但这样执行 javascript 的性能非常低下。

  2. 调试:由于是动态执行字符串,若遇到错误调试器无法捕获错误源,导致模板 BUG 调试变得异常痛苦。在没有进行容错的引擎中,局部模板若因为数据异常甚至可以导致整个应用崩溃,随着模板的数目增加,维护成本将剧增。

如果我们 JavaScript 代码中直接保存 _.template 的结果,那么以上两个问题就不复存在。而 _.template(jstText).source 则保存了 _.template(jstText) 返回的方法字符串。

JST is a server-side thing, not client-side. This mean that you compile Unserscore template on server side by some server-side script and save the result in a file. Then use this file as compiled Unserscore template.

小结

关于 _.template 方法的具体实现,可以参考楼主的 underscore-1.8.3.js 源码解读全文注释版 ,全局搜索即可。

关于前端模板引擎,其实楼主也是个初学者,学习过程中搜到的资料与大家分享下,有机会一定要用下各种模板引擎然后分析下。

@maobon
Copy link

maobon commented Feb 12, 2017

很好的一篇underscore.js的软文...

@mqy1023
Copy link

mqy1023 commented Apr 18, 2017

变量怎么放到class样式中呢?比如返回图标数组,根据不同名指定class样式显示不同的图标,谢谢

@manooog
Copy link

manooog commented Aug 22, 2017

不错哦 看了之后知道循环怎么写了~~

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