lessfish / underscore-analysis

【NO LONGER UPDATE】underscore-1.8.3.js 源码解读 & 系列文章(完)
MIT License
3.96k stars 642 forks source link

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

Open lessfish opened 8 years ago

lessfish commented 8 years ago

前文 浅谈 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 commented 7 years ago

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

mqy1023 commented 7 years ago

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

manooog commented 7 years ago

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