前言
underscore的版本是1.13.7
underscore的链式调用代码分析
让我们看下几个官方例子, 先调用chain函数,然后就可以链式调用数组的一些方法来处理数据,最后通过value()来拿到输出值。对于数组格式的数据处理非常方便,咋们只需要关心数据的输入和输出。
scss
_.chain([1, 2, 3]).reverse().value();
// [3, 2, 1]
_.chain([1,2,3,200])
.filter(function(num) { return num % 2 == 0; })
.map(function(num) { return num * num })
.value();
// [4, 40000]
_.chain(lyrics)
.map(function(line) { return line.words.split(' '); })
.flatten()
.reduce(function(counts, word) {
counts[word] = (counts[word] || 0) + 1;
return counts;
}, {})
.value();
// {lumberjack: 2, all: 4, night: 2 ... }
源码解读
按照chain的使用一步一步的解读源码
1. chain.js文件源码解读
主要目的是导出一个实例对象,{ _chain: true, _wrapped: [1, 2, 3], ...}这种结构
javascript
// 从underscore.js文件中导入underscore函数
import _ from './underscore.js';
// Start chaining a wrapped Underscore object.
export default function chain(obj) {
// 执行该函数,获得一个实例对象
var instance = _(obj);
// 该实例对象上挂载_chain私有属性
instance._chain = true;
// 返回该实例对象,
return instance;
}
2. underscore.js文件源码解读
声明了一个函数,并在该函数的原型对象上处理了value、toString等方法,最后返回把自己作为构造函数new的一个实例对象
javascript
import { VERSION } from './_setup.js';
// 导出一个函数
export default function _(obj) {
// 如果obj就是当前函数实例化的对象,直接返回
if (obj instanceof _) return obj;
// 自己调用自己实例化一个对象返回
if (!(this instanceof _)) return new _(obj);
// 将obj传参挂载到_wrapped私有属性上
this._wrapped = obj;
}
// 记录_函数的版本
_.VERSION = VERSION;
// 在_函数的原型上挂载value方法,返回的值就是this._wrapped
_.prototype.value = function() {
return this._wrapped;
};
// 原型上valueOf和toJSON方法都指向value方法
_.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
// 重写了toString方法,返回this._wrapped到字符串格式
_.prototype.toString = function() {
return String(this._wrapped);
};
3. underscore-array-methods.js文件源码解读
在"_函数"原型上挂载数组原型的一些方法
javascript
// 导入上文2中的underscore.js中的函数
import _ from './underscore.js';
// each是一个便利函数,类似于forEach
import each from './each.js';
import chainResult from './_chainResult.js';
// 数组的原型对象
var ArrayProto = Array.prototype
// Add all mutator `Array` functions to the wrapper.
// 在"_函数"原型上挂载数组原型的一些方法
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
// 引用数组原型的方法
var method = ArrayProto[name];
// 在_函数的原型上声明这些方法
_.prototype[name] = function() {
var obj = this._wrapped;
if (obj != null) {
// 注意obj此时是一个数组,apply的第一传参是obj
// 例如执行代码let a = [1,2,3], Array.prototype.push.apply(a, [4])
// 执行之后a的值就变成[1,2,3,4]了
method.apply(obj, arguments);
// 这里大概意思就是兼容IE低版本的写法,具体参照引用文档讲解
if ((name === 'shift' || name === 'splice') && obj.length === 0) {
delete obj[0];
}
}
// 最后调用了chainResult函数,代码解读看下一个小点
return chainResult(this, obj);
};
});
// 这里为啥要把'concat', 'join', 'slice'单独提出来呢 ?
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
_.prototype[name] = function() {
var obj = this._wrapped;
// 'concat'、'slice'返回新的数组,join返回新的字符串,obj不会变,只好赋值了
if (obj != null) obj = method.apply(obj, arguments);
return chainResult(this, obj);
};
});
export default _;
4. _chainResult.js代码解读
如果instance上有_chain属性,构造一个新的实例返回,否则返回obj, 此时就有问题了,为啥不直接返回this,而要返回_(obj).chain()呢?因为obj的值不一定和instance._wrapped相同了!
javascript
import _ from './underscore.js';
// Helper function to continue chaining intermediate results.
export default function chainResult(instance, obj) {
// 用obj重新创建一个实例,是instance的更新版,
return instance._chain ? _(obj).chain() : obj;
}
如何自己封装一个链式调用函数?
仿照上文简单的封装一个MyChain对象
javascript
class MyChain {
constructor(arg = []) {
this.arg = arg
this.addProp()
}
get value() {
return this.arg
}
addProp() {
['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'].forEach((funName) => {
this[funName] = function () {
Array.prototype[funName].apply(this.arg, arguments)
return this
}
})
const propList = ['concat', 'slice']
propList.forEach(funName => {
this[funName] = function () {
this.arg = Array.prototype[funName].apply(this.arg, arguments)
return this
}
})
}
}
const chain = new MyChain()
console.log(chain.push(1, 2, 3).reverse().value) // [3,2,1]
总结
加深了链式调用的封装理解,如有问题,欢迎指出,Thanks