理解Underscore中的_.template函数
Underscore中提供了_.template函数实现模板引擎功能,它可以将JSON数据源中的数据对应的填充到提供的字符串中去,类似于服务端渲染的模板引擎。接下来看一下Underscore是如何实现模板引擎的。
工具准备
首先是_.template函数的配置项,Underscore源码中配置了默认的配置项:
_.templateSettings = {
// 执行JavaScript语句,并将结果插入。
evaluate: /<%([\s\S]+?)%>/g,
// 插入变量的值。
interpolate: /<%=([\s\S]+?)%>/g,
// 插入变量的值,并进行HTML转义。
escape: /<%-([\s\S]+?)%>/g
};
每一项的意思都写在了注释中,修改不同项的正则表达式,可以修改你传入的字符串模板中的占位符。默认的占位符:
- <% %> : 表示执行JavaScript语句。
- <%= %> : 表示插入变量的值。
- <%- %> : 表示对插入值进行HTML转义后再插入。
源码中还写了一个不可能匹配的正则表达式:
// 一个不可能有匹配项的正则表达式。
var noMatch = /(.)^/;
一个JSON(类字典),用于映射转义字符到转义后的字符:
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
};
以及一个匹配转义字符的正则表达式和一个转义函数。
// 匹配需要转义字符的正则表达式。
var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; // 返回字符对应的转义后的字符。
var escapeChar = function (match) {
return '\\' + escapes[match];
};
接下来会使用到这些变量。
实现_.template
实现原理大致如下:
- 使用正则匹配传入字符串中的所有占位符,并读取占位符中的变量名或JavaScript语句。
- 构造一个字符串,用于定义渲染函数,把读取到的变量名或JavaScript语句嵌入到字符串中,使得在使用渲染函数时,变量会被具体的值替代。如果变量名所代表的值需要转义,则还需使用
_.escape函数对其进行转义,同样写入字符串中。 - 通过步骤二构造的字符串构造渲染函数,定义一个闭包,闭包负责调用这个渲染函数,返回闭包即可。
如何匹配传入字符串中的占位符是一个问题,因为一个字符串中可能包含多种或者多个占位符,这里用了String.prototype.replace方法的一种不常用的方法。
通常,我们至少用String.prototype.replace的简单用法,即第一个参数为要替换的字符串,第二个参数为用于替换它的新字符串,该函数返回替换结果,不改变原字符串。如果要将字符串中的指定字符串全部替换,那么第一个参数应该传入正则表达式,并且采用全局匹配模式g。很多人不知道String.prototype.replace还有更加灵活的第三种用法,即第二个参数传递为一个函数,这个函数的返回结果作为替代指定字符串的新字符串,且至少接收一个参数:
- 如果对于第一个参数没有匹配结果,那么回调函数只接受一个参数,即为原字符串。
- 如果有匹配结果,那么接收至少三个参数,依次为匹配到的字符串、匹配字符串的开始索引和原字符串。
可以打开浏览器输入如下代码回车进行验证:
let str = 'abc';
str.replace(/a/g, function() {
console.log(arguments);
});
如果有多个匹配结果,那么回调函数会被调用多次:
let str = 'abcabc';
str.replace(/a/g, function() {
console.log(arguments);
});
回车之后,在控制台可以看到两次打印结果。那么就相当于是进行了一个循环操作,这个循环会遍历匹配到的每一项,这样就可以对于匹配到的占位符进行适当的操作了。此外,String.prototype.replace函数还有一个很优秀的特性,如果第一个参数传递为正则表达式并且含有多个捕获组(及括号),那么每个捕获组所捕获的字符串都会作为参数传递给回调函数,所以说回调函数至少接收一个参数。其参数个数可以取决于正则表达式中的捕获组个数。验证以下代码:
let str = 'abcabcabc';
str.replace(/(ab)|(c)/g, function() {
console.log(arguments);
});
可以发现回调所接受的参数个数即为3 + 正则中的捕获组个数。基于这个特性,Underscore作者对字符串进行了很好的处理。
实现代码如下:
_.template = function (text, settings, oldSettings) {
// 如果第二个参数为null或undefined。。等,那么使用oldSettings作为settings。
if (!settings && oldSettings) settings = oldSettings;
// 如果三个参数齐整,那么使用整合后的对象作为settings。
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
// 匹配占位符的正则表达式,将配置项中的三个正则合并,每一个正则都是一个捕获组,如果配置项没有包含的话,就默认不匹配任何值。
var matcher = RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
var index = 0;
var source = "__p+='";
// function回调作为string.replace的第二个参数会传递至少三个参数,如果有多余捕获的话,也会被作为参数依次传入。
// string.replace只会返回替换之后的字符串,但是不会对原字符串进行修改,下面的操作实际上没有修改text,只是借用string.replace的回调函数完成新函数的构建。
text.replace(matcher, function (match, escape, interpolate, evaluate, offset) {
// 截取没有占位符的字符片段,并且转义其中需要转义的字符。
source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
// 跳过占位符,为下一次截取做准备。
index = offset + match.length;
// 转义符的位置使用匹配到的占位符中的变量的值替代,构造一个函数的内容。
if (escape) {
// 不为空时将转义后的字符串附加到source。
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} else if (interpolate) {
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} else if (evaluate) {
// 由于是直接执行语句,所以直接把evaluate字符串添加到构造函数的字符串中去就好。
source += "';\n" + evaluate + "\n__p+='";
}
// Adobe VMs need the match returned to produce the correct offset.
// 正常来说没有修改原字符串text,所以不返回值没有关系,但是这里返回了原匹配项,
// 根据注释的意思,可能是为了防止特殊环境下能够有一个正常的offset偏移量。
return match;
});
source += "';\n";
// source拼凑出了一个函数定义的所有内容,为后面使用Function构造函数做准备。
// If a variable is not specified, place data values in local scope.
// 指定作用域,以取得传入对象数据的所有属性。
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n';
var render;
try {
// 通过new Function()形式构造函数对象。
// new Function(param1, ..., paramN, funcBody)
render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
var template = function (data) {
return render.call(this, data, _);
};
// Provide the compiled source as a convenience for precompilation.
var argument = settings.variable || 'obj';
// 为template函数添加source属性以便于进行预编译,以便于发现不可重现的错误。
template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
具体注释都已经写在代码中。
可以发现,在_.template函数中,将配置项中的三个正则合并成了一个,并且每一个正则都构成了一个捕获组,这样回调就会接受6个参数,最后一个参数被作者忽略了。在回调中,作者分别对三种匹配项进行了处理,然后拼接到了source字符串中。
构造完source字符串之后,作者就使用了new Function()的语法构造了一个render函数,通过研究source字符串可以发现,render实际上相当于函数:
function render(settings.variable || 'obj', _) {
var __t,
__p = '',
__j = Array.prototype.join,
print = function(){
__p+=__j.call(arguments,'');
};
// 如果配置了variable属性就不需要使用with块了。
with(obj || {}) {
__p += '...' + ((__t=(" + /*需要转义的变量*/ + "))==null?'':_.escape(__t)) + ((__t=(" + /*变量*/ + "))==null?'':__t) + ... ;
/*需要执行的JavaScript字符串*/;
}
return __p;
}
构造完这个render函数,基本的工作也就完成了。
这里比较巧妙的点在于作者通过String.prototype.replace函数构造函数字符串,对于每一个特定的模板定制了一个特定的函数,这个函数会构造一个对应于模板的字符串,将变量填充进去,所以返回的字符串即为我们想要的字符串。
理解Underscore中的_.template函数的更多相关文章
- 理解Underscore中的_.bind函数
最近一直忙于实习以及毕业设计的事情,所以上周阅读源码之后本周就一直没有进展.今天在写完开题报告之后又抽空看了一眼Underscore源码,发现上次没有看明白的一个函数忽然就豁然开朗了,于是赶紧写下了这 ...
- 理解Underscore中的节流函数
上一篇中讲解了Underscore中的去抖函数(_.debounced),这一篇就来介绍节流函数(_.throttled). 经过上一篇文章,我相信很多人都已经了解了去抖和节流的概念.去抖,在一段连续 ...
- 理解Vue中的Render渲染函数
理解Vue中的Render渲染函数 VUE一般使用template来创建HTML,然后在有的时候,我们需要使用javascript来创建html,这时候我们需要使用render函数.比如如下我想要实现 ...
- 深入理解javascript中的立即执行函数(function(){…})()
投稿:junjie 字体:[增加 减小] 类型:转载 时间:2014-06-12 我要评论 这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是 ...
- 深入理解javascript中的立即执行函数
这篇文章主要介绍了深入理解javascript中的立即执行函数,立即执行函数也叫立即调用函数,通常它的写法是用(function(){…})()包住业务代码,使用jquery时比较常见,需要的朋友可以 ...
- 理解Underscore中的uniq函数
uniq函数,是Underscore中的一个数组去重函数,给它传递一个数组,它将会返回该数组的去重副本. 1 ES6版本去重 在ES6版本中,引入了一个新的数据结构——set,这是一种类似数组的数据结 ...
- 理解Underscore中的restArgs函数
虽然Underscore并没有在API手册中提及到restArgs函数,我们仍然可以通过_.restArgs接口使用restArgs函数.如果不去阅读源码,我们很难发现Underscore中还有这样的 ...
- 理解Underscore中的flatten函数
最近是在所在实习公司的第一个sprint,有个朋友又请假了,所以任务比较重,一直这么久都没怎么更新了,这个周末赖了个床,纠结了一会儿决定还是继续写这个系列,虽然比较乏味,但是学到的东西还是很多的. 之 ...
- 理解JavaScript中的去抖函数
何为去抖函数?在学习JavaScript去抖函数之前我们需要先弄明白这个概念.很多人都会把去抖跟节流两个概念弄混,但是这两个概念其实是很好理解的. 去抖函数(Debounce Function),是一 ...
随机推荐
- Windows下SVN回滚到旧版本(TortoiseSVN)
当发现新提交的代码有问题,然后想将某个旧的版本作为最新的版本时,可以使用回滚, 操作步骤如下: 1. 签出(CheckOut)最新版本的代码到电脑中(不能做任何修改) 2. 执行TortoiseSVN ...
- ES6学习准备
ES6学习准备 选择运行环境 ES6的语法,nodeJs.浏览器不一定都支持,不同版本的支持情况不一样.在学习过程中,如何确定是自己写的代码有问题,还是运行环境不支持呢? 首先,浏览器端一般支持的特性 ...
- ExtJs6内嵌iframe,nginx部署本地前台文件
/** * Created by Wwei on 2016/9/1. */ Ext.define('Admin.view.photo.CADMultiUploadForm', { extend: 'E ...
- Netezza External Tables --How to use local files in external table
FROM: http://tennysusantobi.blogspot.com/2012/08/netezza-external-tables.html Netezza External Table ...
- MYSQL连接字符串参数解析(解释)
被迫转到MySQL数据库,发现读取数据库时,tinyint类型的值都被转化为boolean了,这样大于1的值都丢失,变成true了.查阅资料MySQL中无Boolean类型,都是存储为tinyint了 ...
- The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the associated filter.
The Struts dispatcher cannot be found. This is usually caused by using Struts tags without the assoc ...
- Java并发编程:volatile关键字解析(学习总结-海子)
博文地址:Java并发编程:volatile关键字解析
- java下double相乘精度丢失问题
比如 System.out.println(0.14*100); 输出: 14.000000000000002 解决方法: BigDecimal b = new BigDecimal(String.v ...
- js中random的应用
1.生成一个随机数 var r = Math.random(); console.info(r); 结果生成一个0-1的随机数(返回0和1之间的伪随机数,可能为0,但总是小于1,[0,1)) 2.生成 ...
- 用一个小例子来谈谈javascript的运行机制
先上例子! <script type="text/javascript"> console.log('博'); setTimeout(function(){ conso ...