摘要:学习冴羽的博客研究 underscore 实现一个模板引擎
思路
underscore 的 template 函数参考了 jQuery 的作者 John Resig 在 2008 年发表的一篇文章 JavaScript Micro-Templating
简单例子:
1 | <script type="text/html" id="tempId"> |
John Resig 的思路是将其转换成一段拼接成的 js 字符串,最后运行这段字符串:
1 | // 模拟数据 |
如何替换才能实现这样的转换?重点来了:
1、将 %> 替换成 p.push('
2、将 <% 替换成 ');
3、将 <%=xxx%> 替换成 ');p.push(xxx);p.push('
转换后的模板是:
1 | ');for ( var i = 0; i < users.length; i++ ) { p.push(' |
补全其他部分代码:
1 | // 添加头部代码 |
这样就得到一段完整到代码:
1 | var p = [];p.push(''); |
简单实现
使用正则进行替换操作:
1 | function tmpl(templateId, data) { // 模板和传入的数据 |
这里使用 eval 解析代码,实际上 John Resig 在文章中使用的是 Function 构造函数(Function与eval)。要注意的是: Function 是全局作用域,而 eval 可以访问当前作用域
Function 构造函数版:
1 | function tmpl(templateId) { |
使用 with
当传入的输入结构比较复杂时,总是需要使用 data.xxx 的形式来获取。但使用 with 可以简化路径,直接调用 xxx 即可,而不需要频繁使用 data. 来获取。with用法查看with
1 | var data = { |
优化
每次调用 tmpl 函数都要调用 new Function,这样效率很低。如果调用 tmpl 函数时返回一个函数,使用该函数时传入不同的数据就可以返回不同的 html 字符串,则可以提高效率
1 | function tmpl(templateId) { |
完整示例:
1 | <!DOCTYPE html> |
underscore模板引擎的实现
underscore 模板引擎使用的是字符串拼接,而不是使用数组的 push 后再 join 的方法
方法步骤:
1、将 <%=xxx%> 替换成 ‘+ xxx +’
2、将 <%xxx%> 替换成 ‘; xxx __p+=’
3、添加首尾代码
还是那个简单例子:
1 | <script type="text/html" id="tempId"> |
先进行替换:
1 | '; for ( var i = 0; i < users.length; i++ ) { __p+=' |
然后添加上首尾代码:
1 | var __p=''; |
这样就得到了完整的字符串__p。但是实际上还有很多细节问题要考虑,其中之一就是特殊字符需要处理。比如转义字符中的行终结符、转义字符前面的斜杠\以及拼接字符串使用单引号'等,这些都有可能使拼接后的字符串__p并不是一个我们能够直接使用的标准字符串(查看转义序列):在执行构造函数 Function 时行终结符可以导致换行,斜杠\可以导致转义,单引号'可能会与模板的其他内容里含有的单引号进行错误匹配。因而,需要在替换之前就现将模板里的内容进行处理。
之前使用/<%=(.*?)%>/g来匹配符合<%=xxx%>的所有字符,但是.匹配的是除行终结符之外的任何单个字符,真正要匹配任意字符需要使用 [\d\D]、[\w\W]、[\s\S] 或 [^] (查看《JS正则迷你书(1.1版)》笔记)。
1 | // 匹配规则 |
处理完特殊字符,还有一类特殊值要处理:如果模板里的某个变量在提供的数据里是没有的的话会显示 undefined,但实际上我们期望它是空字符串。这里我们要对所有属性添加一个判断:当该属性不存在时就设置为空字符串
1 | .replace(settings.interpolate, function(match, interpolate){ |
实际上 underscore 使用了这样的方法:
1 | var source = "var __t, __p='';\n"; |
而且 underscore 使用 replace 方法只进行了一次匹配:
1 | var settings = { |
思路就是把依据 replace 第一个参数是正则表达式,并且其为全局匹配模式时,第二个参数的函数会被多次调用(《JS正则迷你书(1.1版)》笔记 2) 替换),不断复制字符串,处理字符串,拼接字符串,最后拼接首尾代码,得到最终的代码字符串
正则表达式的 source 属性返回正则的内容字符串,即两个斜杠之间的内容(《JS正则迷你书(1.1版)》笔记 10) source 属性)。此外,只匹配要替换的变量的话会造成模板最后一个变量之后的代码丢失,因而还要匹配最后位置$以便 slice(index, offset) 截取到最后一个字符。
完整代码:
1 | <!DOCTYPE html> |
至此,代码跟 underscore 里的实现已经很接近了,但它还有更多的细节处理。具体请看Underscore.js 1.9.2