前言
underscore提供了模板引擎的功能
1 | var tpl = 'hello : <%= name %>'; |
感觉好像没有什么强大的地方,再举个例子
在 HTML 文件中:
1 | <ul id="name_list"></ul> |
javaScript 文件中:
1 | var container = document.getElementById('name_list'); |
实现思路
underscore的template函数参考了jQuery的作者john Resig 在2008年发表的一篇文章 javaScript Micro-Templating
依然是以这段模板字符串为例:
1 | <%for ( var i = 0; i < users.length; i++ ) { %> |
john Resig 的思路是将这段代码转换成这样一段程序:
1 | // 模拟数据 |
我们注意到,模板其实就是一段字符串,我们怎么根据一段字符串生成一段代码呢?很容易就想到用eval,那就是用eval吧,
然后我们会发现,为了转换成这样一段代码,我们需要将<%xxx%>转换为 xxx ,其实也就是去掉包裹的符号,还要将 <%=xxx%> 转化成 p.push(xxx),这些都可以用正则实现,但是我们还需要写 p.push(‘
那我们换个思路,依然是用正则,但是我们
- 将 %> 替换成 p.push(‘
- 将 <% 替换成 ‘);
- 将 <%=xxx%> 替换成 ‘);p.push(xxx);p.push(‘
来举个例子
1 | <%for ( var i = 0; i < users.length; i++ ) { %> |
照着这个规则,上面的代码会被替换成
1 | ');for ( var i = 0; i < users.length; i++ ) { p.push(' |
这样肯定会报错,毕竟代码都没有写全,我们在首和尾加上部分代码,变成:
1 | // 添加的首部代码 |
整理下这段代码:
1 | var p = []; p.push(''); |
恰好可以实现这个功能,不过还要注意一点,要将换行符替换成空格,防止解析成代码的时候报错,不过为了这里能够方便理解原理,就只在代码中实现。
第一版
1 | // 第一版 |
为了验证是否有用:
HTML文件:
1 | <script type="text/html" id="user_tmpl"> |
javaScript文件
1 | var users = [ |
Function
在这里我们使用了 eval, 实际上 john Resig 在文章中使用的是Function构造函数。
Function 构造函数创建一个新的Function对象,在JavaScript中,每个函数实际上都是一个Function对象。
使用方法为:
1 | new Function([arg1[, arg2[, ...argN]], ] functionBody) |
arg1, arg2, … argN 表示函数用到的参数,functionBody表示一个含有包括函数定义的JavaScript语句的字符串。
1 | var adder = new Function("a", "b", "return a + b"); |
第二版
使用Function构造函数:
1 | // 第二版 |
使用方法依然跟第一版相同
不过值得注意的是:其实tmpl函数没有必要传入data参数,也没有必要在最后return的时候,传入data参数,即使你把这两个参数都去掉,代码还是可以正常执行的。
使用Function构造器生成的函数,并不会在创建它们的上下文中创建闭包;它们一般在全局作用域中被创建。当运行这些函数的时候,它们只能访问自己的本地变量和全局变量,不能访问Function构造器被调用生成的上下文的作用域。这和使用带有函数表达式代码的eval不同。这也就是为什么去掉参数后,它依然能正常执行,本地变量没有users,便使用到了全局变量users,从而获取到了数据
with
现在有一个小问题,实际上我们传入的数据结构可能比较复杂,如:
1 | var data = { |
如果我们将这个数据结构传入tmpl函数中,在模板字符串中,如果要用到某个数据,总是需要使用data.name
、data.friends
、的形式来获取,麻烦就麻烦在我想直接使用name、friends等变量,而不是繁琐的使用 data. 来获取。
这又该如何实现呢?答案是with
with语句可以扩展一个语句的作用域链(scope chain)。当要多次访问一个对象的时候,可以使用with做简化。比如:
1 | var hostName = location.hostname; |
1 | function Person() { |
然而并不推荐使用with语句, 因为他可能是混淆错误和兼容性问题的根源,除此之外,也会造成性能低下
第三版
使用了 with
1 | // 第三版 |
第四版
如果我们的模板不变,数据却发生了变化,如果使用我们之前写的tmpl函数,每次都会new Function,这其实是没有必要的,如果我们能在使用tmpl的时候,返回一个函数,然后使用该函数,传入不同的数据,只根据数据不同渲染不同的html字符串,就可以避免这种无所谓的损失。
1 | // 第四版 |