前言
underscore是一个JavaScript工具库,提供了一整套函数式编程的实用功能.其内部包含了很多工具函数比如each,map,reduce,filter等等.虽然es5,es6已经包含其中大部分,但查看源码了解这些函数底层的实现有助于加深对原生js的理解.
underscore内部定义的都是纯函数,并支持单向数据源的链式调用.在众多函数组成的链条中,可以神奇的发现数据就像经过管道一样从一个函数流向另一个函数.
const result = _([1, 2, 3, 4, 5, 6, 7]).chain().map((item) => {return item * 2;}).filter((item) => {
return item > 10;
}).value();
运行过程如下:
- 将数组作为起始数据源,运行_([1, 2, 3, 4, 5, 6, 7])生成underscore的实例对象,再调用实例上的方法chain使其具备链式调用能力
- 运行map函数将数组每个元素都乘以2组成新数组返回
- 运行filter函数时输入数据已经变成[2, 4, 6, 8, 10, 12, 14],过滤掉小于10的值
- 最终运行value()方法获取结果[12,14]
以上过程看出,数据源从最初端口进入,一层层向后传递,每个函数会接收到前一个函数处理完毕的结果,又会向下一个函数发送自己运行完的结果.数据就像进入管道一样,像流水一般往后流动,如此便形成了流式调用.接下来实现它的整体运作机制.
源码实现
创建构造函数
定义构造函数_,如果obj是一条普通数据,运行_(obj) 时,this指向了window,结果返回实例对象 new _(obj),那么该对象包含属性wrapped,对应的值为obj.
(function (global) {
function _(obj) {
if (obj instanceof _) {
return obj;
}
if (!(this instanceof _)) {
return new _(obj);
}
this.wrapped = obj;
}
_.prototype.value = function () {
return this.wrapped;
};
global._ = _;
})(window);
实现流式调用
定义一个对象allExports,将定义的工具函数赋予该对象的属性上,并传入mixin函数运行
function chain(obj) {
const _instance = _(obj);
_instance._chain = true;
return _instance;
}
function map(obj, iteratee) {...}
function filter(obj, iteratee) {...}
const allExports = {
chain,
map,
filter,
};
mixin(allExports);
运行mixin函数,参数obj便是上方定义的allExports.获取所有函数名组成数组array进行遍历.key是函数名,func对应具体的函数.
func并没有直接绑定在构造函数上,而是绑定在构造函数的原型对象上.从这里可以看出,_(data).chain().map().filter()执行过程里,调用chain,map,filter时其实调用的是定义在mixin里面挂载在原型对象上的函数
执行流程如下:
- 假设data = [1,2,3],_(data)的结果便是实例对象{wrapped:[1,2,3]}
- 调用实例对象的chain方法,运行下方函数.result = [[1,2,3]].使用push将用户可能传入的参数并入result中
- func此时指向chain函数,运行chain函数给实例对象添加了_chain属性true.
- chainResult函数判端当前实例对象支不支持链式调用,如果支持后续新生成的实例对象都添加上_chain为true.并返回此新实例对象
- 新的实例对象的数据仍然是{wrapped:[1,2,3]}.继续调用map方法运行下方函数,将this.wrapped和用户传递的参数组合起来传入func,func指向了map函数.map函数运行完返回结果[2,4,6].chainResult发现支持链式调用运行_([2,4,6])生成新的实例对象
- 新的实例对象继续调用filter,调用的仍然是下方函数.此时this.wrapped已经变成了[2,4,6].从这里可以看出每运行一个函数,会将函数处理完的数据作为参数生成新的实例对象,并返回此新实例对象继续调用其他函数,又会生成新的实例对象.新的实例对象带着处理后的数据继续调用下去,就形成了数据的流动.
function mixin(obj) {
const array = Object.keys(obj);
array.forEach((key) => {
const func = obj[key];
_.prototype[key] = function () {
const result = [this.wrapped];
Array.prototype.push.apply(result, arguments);
return chainResult(this, func.apply(_, result));
};
});
}
function chainResult(_instance, obj) {
return _instance._chain ? _(obj).chain() : obj;
}
完整代码
(function (global) {
function _(obj) {
if (obj instanceof _) {
return obj;
}
if (!(this instanceof _)) {
return new _(obj);
}
this.wrapped = obj;
}
_.prototype.value = function () {
return this.wrapped;
};
function chain(obj) {
const _instance = _(obj);
_instance._chain = true;
return _instance;
}
//map函数的简单实现,支持数组和对象
function map(obj, iteratee) {
const keys = !Array.isArray(obj) && Object.keys(obj);
const len = keys ? keys.length : obj.length;
const result = [];
for (let i = 0; i < len; i++) {
const current_key = keys ? keys[i] : i;
result.push(iteratee(obj[current_key]));
}
return result;
}
//filter函数的简单实现,,支持数组和对象
function filter(obj, iteratee) {
const keys = !Array.isArray(obj) && Object.keys(obj);
const len = keys ? keys.length : obj.length;
const result = [];
for (let i = 0; i < len; i++) {
const current_key = keys ? keys[i] : i;
if (iteratee(obj[current_key])) {
result.push(obj[current_key]);
}
}
return result;
}
function mixin(obj) {
const array = Object.keys(obj);
array.forEach((key) => {
const func = obj[key];
_.prototype[key] = function () {
const result = [this.wrapped];
Array.prototype.push.apply(result, arguments);
return chainResult(this, func.apply(_, result));
};
});
}
function chainResult(_instance, obj) {
return _instance._chain ? _(obj).chain() : obj;
}
const allExports = {
chain,
map,
filter,
};
mixin(allExports);
global._ = _;
})(window);