概述

很早就想研究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

模板引擎

之前就接触过模板引擎,比如说template.js、handlebars.js、jade.js、nunjucks.js等等。后来学react的时候也接触过jsx,当时我就感到很不可思议,竟然能够把js中的变量甚至语句插入到html里面去,真的十分神奇。今天看underscore.js的源码的时候也发现里面竟然有模板引擎,于是我就来研究研究模板引擎。

实现变量替换

说到模板引擎,一个最基本的特性就是能在html代码中插入js的变量,下面我们来实现这种效果。

我们需要实现这种效果:

//定义一个模板
const tpl = 'hello {{name}}'; //定义值
const data = {name: 'haha'}; //渲染,最后content是name被替换过的html代码
const output = render(tpl, data);

其实仔细理了一下效果的流程之后,感觉实现这个效果挺简单的,就是用一个正则替换,把{{ name }}里面的值替换为data里面的数据就行了。

实现代码如下:

//定义替换的正则表达式
const rule = /{{([\s\S]+?)}}/g; //render函数
function render(tpl, data) {
return tpl.replace(rule, (matcher, p1) => {
return data[p1];
})
}

注意,由于rule里面只有一个括号,所以replace第二个参数的函数里面只有p1没有p2。

变量替换改进

为了便于阅读,我们需要模板可以写成下面的形式。(name两边有空格)

//定义一个模板
const tpl = 'hello {{ name }}';

所以我们加一个去空格的函数,整个代码如下:

//定义替换的正则表达式
const rule = /{{([\s\S]+?)}}/g; //render函数
function render(tpl, data) {
return tpl.replace(rule, (matcher, p1) => {
return data[p1.trim()];
})
}

注意:trim函数只兼容IE9,如果要兼容IE9以下的话就需要pollyfill了。

支持语句

几乎所有的模板引擎都支持写入语句,比如像下面的写法:

const tpl = 'Students:' +
//注意这里只有一个大括号!!!
'{ for(i = 0; i < data.students.length; i++) }' +
'{{ data.students[i].name }}';
const data = {
students: [{
id: 1,
name: ' haha '
},{
id: 2,
name: ' yaya '
}]
};
const content = render(tpl, data);

我们希望上述代码输出:

Students: haha  yaya

看起来非常复杂,可是我们用伪代码分解一下执行过程就感觉有点简单了:

//首先输出Students:
//然后执行下面的代码
for(i = 0; i < data.students.length; i++) {
//这里输出students[i].name
}
//完毕

实际写起来是这样的:

//定义要输出的内容
content = '';
content += 'Students:';
for(i = 0; i < data.students.length; i++) {
content += 'data.students[i].name';
}
//输出整个content
return content

可以看到,上面有这2个要点:

  1. 变量的内容需要添加到content里面,但是语句的内容不需要添加到content里面。
  2. 不是模板的内容要记录位置,然后再通过这个位置添加到content里面。

所以好好整理一下,我们首先需要语句的正则表达式,然后通过这个正则表达式按照上述规则进行替换,代码如下:

//为了方便,我们把规则封装在一个对象里面
const rules = {
//插值,对应变量
interpolate: /{{([\s\S]+?)}}/,
//逻辑,对应语句
evaluate: /{([\s\S]+?)}/
};
//2个正则合在一起,先替换变量,再替换语句
const matcher = new RegExp([
rules.interpolate.source,
rules.evaluate.source
].join('|'), 'g'); //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);
}

它生成的renderFunc函数的代码如下图所示:

(function(obj
/*``*/) {
let content = "";
content += "Students:";
for(i = 0; i < data.students.length; i++)
content += data.students[i].name ;
return content;
})

可以看到有一个缺点,就是for循环没有大括号,这就导致它只执行下面的那条语句。如果要加大括号的话,就需要额外的规则,我们这里不讨论。

所以把上面所有的代码加起来就是这样的:

//为了方便,我们把规则封装在一个对象里面
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].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: haha yaya
console.log(render(tpl, data));

可以看到,整个过程实际上是在拼接和替换字符串,然后利用Function接受字符串的情形生成函数,没有其他的任何内容。

在下一篇博文中我们会对这个小的模板引擎进行优化。

underscore.js源码研究(5)的更多相关文章

  1. underscore.js源码研究(8)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  2. underscore.js源码研究(7)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  3. underscore.js源码研究(6)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  4. underscore.js源码研究(4)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  5. underscore.js源码研究(3)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  6. underscore.js源码研究(2)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  7. underscore.js源码研究(1)

    概述 很早就想研究underscore源码了,虽然underscore.js这个库有些过时了,但是我还是想学习一下库的架构,函数式编程以及常用方法的编写这些方面的内容,又恰好没什么其它要研究的了,所以 ...

  8. underscore.js源码解析(五)—— 完结篇

    最近公司各种上线,所以回家略感疲惫就懒得写了,这次我准备把剩下的所有方法全部分析完,可能篇幅过长...那么废话不多说让我们进入正题. 没看过前几篇的可以猛戳这里: underscore.js源码解析( ...

  9. underscore.js 源码

    underscore.js 源码 underscore]JavaScript 中如何判断两个元素是否 "相同" Why underscore 最近开始看 underscore.js ...

随机推荐

  1. java JNI 实现原理 (二) Linux 下如何 load JNILibrary

    在博客java JNI (一)虚拟机中classloader的JNILibrary 中讨论了java中的Library 是由classloader 来load的,那我们来看看 classloader是 ...

  2. 关于进行pdf的每页广告去除、转换word等方案。

    pdf转word经常使用的是 软件下载安装破解完成以后进行编辑pdf,可以导出word,效果比一般的word自带的转换效果要好. 在进行pdf的每页去除页脚或者页眉的广告时候,使用pdf的替换功能.这 ...

  3. 20175316盛茂淞-Java第1周学习总结

    20175316盛茂淞 2018-2019-2 <Java程序设计>第1周学习总结 教材学习内容总结 Java入门 1.Java简介(地位,特点) 2.安装JDK,设置系统环境 3.编译J ...

  4. hadoop 修改datanode balance带宽使用限制

    前段时间,一个客户现场的Hadoop看起来很不正常,有的机器的存储占用达到95%,有的机器只有40%左右,刚好前任的负责人走了,这边还没有明确接班人的时候. 我负责的大数据计算部分,又要依赖Hadoo ...

  5. sqlserver2008查询性能优化(文摘)

    第1章 sql查询性能调整 第4章 索引分析

  6. asp.net DataReader DataTable 使用反射给给实体赋值

    asp.net 使用反射给给实体赋值 实体类继承此基类 using System.Reflection; using System.Data.SqlClient; using System.Data; ...

  7. (转)eclipse下配置tomcat7的几个重要问题,值得一看

    转自:http://jingyan.baidu.com/article/ab69b270ccc4792ca7189fd6.html 这段时间开始接触的servlet,今天尝试在eclipse下配置to ...

  8. (最短路)Silver Cow Party --POJ--3268

    题目链接: http://poj.org/problem?id=3268 题意: 先求出所有牛到x的最短路,再求出x到所有牛的最短路,两者相加取最大值(单向图)(可以用迪杰斯特拉,SPFA) 迪杰斯特 ...

  9. POJ2289 Jamie's Contact Groups(二分图多重匹配)

    Jamie's Contact Groups Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 7721   Accepted: ...

  10. Q他中的乱码再理解

    Qt版本有用4的版本的也有用5的版本,并且还有windows与linux跨平台的需求. 经常出现个问题是windows的解决了,源代码放到linux上编译不通过或者中文会乱码,本文主要是得出一个解决方 ...