underscore.js源码研究(6)
概述
很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以就了结研究underscore源码这一心愿吧。
underscore.js源码研究(1)
underscore.js源码研究(2)
underscore.js源码研究(3)
underscore.js源码研究(4)
underscore.js源码研究(5)
underscore.js源码研究(6)
underscore.js源码研究(7)
underscore.js源码研究(8)
参考资料:underscore.js官方注释,undersercore 源码分析,undersercore 源码分析 segmentfault
模板引擎的改进
这篇博文主要是对之前建立的小模板引擎做一些改进。
支持大括号
理论上来说,如果按照下面的写法写tpl,就能够实现输出大括号了:
//定义模板和数据
const tpl = 'Students:' +
//注意这里后面有一个大括号
'{ for(i = 0; i < data.students.length; i++){ }' +
'{{ data.students[i].name }}' +
//插入语句,这个语句是一个大括号
'{ } }';
但是实际匹配的时候,会匹配{}而不是{}},所以我们需要改一下正则表达式的规则,按照underscore.js的写法,我们使用ERB风格的规则:
const rules = {
//插值,对应变量
interpolate: /<%=([\s\S]+?)%>/,
//逻辑,对应语句
evaluate: /<%([\s\S]+?)%>/
};
//2个正则合在一起,先替换变量,再替换语句
const matcher = new RegExp([
rules.interpolate.source,
rules.evaluate.source
].join('|'), 'g');
这样就可以实现大括号了,代码如下。(需要先导入上面的代码)
//定义模板和数据
const tpl = 'Students:' +
//注意这里只有一个大括号!!!
'<% for(i = 0; i < data.students.length; i++){ %>' +
'<%= data.students[i].id %>' +
'<%= data.students[i].name %>' +
'<% } %>';
const data = {
students: [{
id: 1,
name: ' haha '
},{
id: 2,
name: ' yaya '
}]
};
//render函数
function render(tpl, data) {
let concating = 'let content = "";\n';
let index = 0;
//仍然是replace里面的第二个参数是函数的形式
tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
//添加非模板的内容
if (tpl.slice(index, offset)) {
concating += 'content += "' + tpl.slice(index, offset) + '";\n';
}
//记录偏移量
index = offset + match.length;
//变量需要添加到content里面
if (interpolate) {
concating += 'content +=' + interpolate + ';\n';
//语句不需要添加到content里面,而且不要分号
} else if (evaluate) {
concating += evaluate + '\n';
}
})
concating += 'return content;';
//以concating为内容,定义一个函数,参数是obj
const renderFunc = new Function('obj', concating);
return renderFunc(data);
}
//输出,结果为Students:1 haha 2 yaya
console.log(render(tpl, data));
它生成的renderFunc函数的代码如下面所示:
(function(obj
/*``*/) {
let content = "";
content += "Students:";
for(i = 0; i < data.students.length; i++){
content += data.students[i].id ;
content += data.students[i].name ;
}
return content;
})
字符串转义
有时候我们希望加入换行符等字符串来调整输出的格式,也就是说我们希望能解析如下所示的模板:
//注意下面的/n换行符
const tpl = 'Students: \n' +
//注意这里只有一个大括号!!!
'<% for(i = 0; i < data.students.length; i++){ %>' +
'<%= data.students[i].id %>' +
'<%= data.students[i].name %>' +
'\n' +
'<% } %>';
然而报错了,原因是我们在用字符串构建函数的过程中,\n导致了换行,所以语句被中断了,导致了报错。解决办法是对\n进行转义为\\n。
首先我们定义转义规则:
//定义转义规则
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
}
//定义转义正则
var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
//定义转义函数
var escapeChar = function(match) {
return '\\' + escapes[match];
}
然后我们重写render函数,使它在添加非模板内容的时候进行转义:
function render(tpl, data) {
let concating = 'let content = "";\n';
let index = 0;
tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
//添加非模板的内容
if (tpl.slice(index, offset)) {
//这里进行转义
concating += 'content += "' + tpl.slice(index, offset).replace(escapeRegExp, escapeChar) + '";\n';
}
//记录偏移量
index = offset + match.length;
//变量需要添加到content里面
if (interpolate) {
concating += 'content +=' + interpolate + ';\n';
//语句不需要添加到content里面,而且不要分号
} else if (evaluate) {
concating += evaluate + '\n';
}
})
concating += 'return content;';
//以concating为内容,定义一个函数,参数是obj
const renderFunc = new Function('obj', concating);
return renderFunc(data);
}
然后我们再来执行,输出如下,正是我们想要的:
Students:
1 haha
2 yaya
我们来看下它生成的renderFunc函数:
(function(obj
/*``*/) {
let content = "";
content += "Students: \n";
for(i = 0; i < data.students.length; i++){
content += data.students[i].id ;
content += data.students[i].name ;
content += "\n";
}
return content;
})
预编译
上面我们在使用这个模板的时候是这么使用的:
console.log(render(tpl, data));
很明显,render函数,tpl模板和data数据耦合在一起了,这就表明假如我们需要修改data数据的话,就要重新调用render函数重新渲染tpl一次,非常的消耗时间。
所以我们打算进行预编译。原理是,在render函数里面,我们利用一个闭包储存concating(即render函数解析tpl的结果),然后下次data改动的时候,直接使用储存的concating数据,而不需要重新编译生成concating数据。代码如下:
//只接受tpl参数
function render(tpl) {
let concating = 'let content = "";\n';
let index = 0;
tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
//添加非模板的内容
if (tpl.slice(index, offset)) {
concating += 'content += "' + tpl.slice(index, offset).replace(escapeRegExp, escapeChar) + '";\n';
}
//记录偏移量
index = offset + match.length;
//变量需要添加到content里面
if (interpolate) {
concating += 'content +=' + interpolate + ';\n';
//语句不需要添加到content里面,而且不要分号
} else if (evaluate) {
concating += evaluate + '\n';
}
})
concating += 'return content;';
//以concating为内容,定义一个函数,参数是obj
const renderFunc = new Function('obj', concating);
//这里我们返回一个函数,形成一个闭包
return function(data){
return renderFunc(data);
};
}
使用起来整个代码是这样的:
//定义转义规则
var escapes = {
"'": "'",
'\\': '\\',
'\r': 'r',
'\n': 'n',
'\u2028': 'u2028',
'\u2029': 'u2029'
}
//定义转义正则
var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
//定义转义函数
var escapeChar = function(match) {
return '\\' + escapes[match];
}
//为了方便,我们把规则封装在一个对象里面
const rules = {
//插值,对应变量
interpolate: /<%=([\s\S]+?)%>/,
//逻辑,对应语句
evaluate: /<%([\s\S]+?)%>/
};
//2个正则合在一起,先替换变量,再替换语句
const matcher = new RegExp([
rules.interpolate.source,
rules.evaluate.source
].join('|'), 'g');
//定义模板
const tpl = 'Students: \n' +
//注意这里只有一个大括号!!!
'<% for(i = 0; i < data.students.length; i++){ %>' +
'<%= data.students[i].id %>' +
'<%= data.students[i].name %>' +
'\n' +
'<% } %>';
//定义数据
let data = {
students: [{
id: 1,
name: ' haha '
},{
id: 2,
name: ' yaya '
}]
};
//render函数
function render(tpl) {
let concating = 'let content = "";\n';
let index = 0;
tpl.replace(matcher, (match, interpolate, evaluate, offset) => {
//添加非模板的内容
if (tpl.slice(index, offset)) {
concating += 'content += "' + tpl.slice(index, offset).replace(escapeRegExp, escapeChar) + '";\n';
}
//记录偏移量
index = offset + match.length;
//变量需要添加到content里面
if (interpolate) {
concating += 'content +=' + interpolate + ';\n';
//语句不需要添加到content里面,而且不要分号
} else if (evaluate) {
concating += evaluate + '\n';
}
})
concating += 'return content;';
//以concating为内容,定义一个函数,参数是obj
const renderFunc = new Function('obj', concating);
//这里我们返回一个函数,形成一个闭包
return function(data){
return renderFunc(data);
};
}
//先进行编译
var tplCompile = render(tpl);
//输出
console.log(tplCompile(data));
//修改数据
data = {
students: [{
id: 1,
name: ' haha '
},{
id: 2,
name: ' yaya '
},{
id: 3,
name: ' jaja '
}]
};
//输出
console.log(tplCompile(data));
输出结果如下:
Students:
1 haha
2 yaya
Students:
1 haha
2 yaya
3 jaja
其它
有时候,我们需要对向模板中插入的值进行转义,或者称为escape。比如说我们插入的是<script>标签,那么我们就需要对<进行转义,这个时候就必须在最开始声明的正则规则中添加第三条规则,我们这里就不讨论了,感兴趣的话可以参考underscore.js源码。
underscore.js源码研究(6)的更多相关文章
- underscore.js源码研究(8)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码研究(7)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码研究(5)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码研究(4)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码研究(3)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码研究(2)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码研究(1)
概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...
- underscore.js源码解析(五)—— 完结篇
最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题. 没看过前几篇的可以猛戳这里: underscore.js源码解析( ...
- underscore.js 源码
underscore.js 源码 underscore]JavaScript 中如何判断两个元素是否 "相同" Why underscore 最近开始看 underscore.js ...
随机推荐
- Linux 在文档中查找满足条件的行并输出到文件:
Linux 在文档中查找满足条件的行并输出到文件: 文件名称: dlog.log 输出文件: out.log 1.满足一个条件(包含 “TJ” )的语句: grep “TJ” dlog. ...
- 多分类评价指标python代码
from sklearn.metrics import precision_score,recall_score print (precision_score(y_true, y_scores,ave ...
- Servlet WebSocket的简易聊天室
添加依赖 <!-- websocket --> <dependency> <groupId>javax.websocket</groupId> < ...
- 连接oracle数据库报错:TNS-12516 TNS:listener could not find available handler with matching protocol stack解决方法
导致此问题的可能原因为:数据库的当前会话说不满足造成的. 解决方法如下: (1)连接数据库: [localhost@oracle]$:sqlplus /nolog sql>conn / as ...
- C的指针和数组
int i; //定义整型变量i int *p; //定义一个指向int的指针变量p int a[5]; //定义一个int数组a int *p[5]; //定义一个指针数组,其中每个数组元素指向一个 ...
- c#文件比较Code
我想我们很多时候想比较一个文件里面是否有改动,比如一个dll库是新加了一个方法或修改了其中的方法实现,不能通过可视化的工具来比较的时候,可以用这个小工具来比较, 以下是比较文件的代码. using S ...
- 华为机试-iNOC产品部-杨辉三角的变形
题目描述 1 1 1 1 1 2 3 2 1 1 3 6 7 6 3 11 4 10 16 19 16 10 4 1以上三角形的数阵,第一行只有一个数1,以下每行的每个数,是恰好是它上面的数,左上角数 ...
- Forward团队-爬虫豆瓣top250项目-代码设计规范
组长地址:http://www.cnblogs.com/mazhuangmz/p/7603641.html 成员:马壮,李志宇,刘子轩,年光宇,邢云淇,张良 1.缩进采用一个Tab键 2.大括号:如: ...
- day37(类加载器)
类的加载器:将class文件加载到JVM中执行这个文件. Java中将类加载器分成三类: 引导类加载器: JAVA_HOME/jre/lib/rt.jar | 扩展类加载器: JAVA ...
- 20155326 《Java程序设计》第8周学习总结
20155326 <Java程序设计>第8周学习总结 教材学习内容总结 NIO (1)NIO使用频道来衔接数据节点,在处理数据时,NIO可以让你设定缓冲区容量,在缓冲区中对感兴趣的数据区块 ...