underscore链式调用,chain()方法
官网简介:Returns a wrapped object. Calling methods on this object will continue to return wrapped objects until value is called.
翻译过来就是,chain() 方法会返回一个包装对象,直到value() 方法被调用之前,对此对象调用方法都会返回该包装对象
啥意思呢,看个官方的例子,筛选stooges数组中年纪最小的人
var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
//不使用chain() 方法
var arr = _.sortBy(stooges, function(stooge){ return stooge.age; }) //按照age大小从小到大排序
arr = _.map(arr, function(stooge){ return stooge.name + ' is ' + stooge.age; })
var item = _.first(arr) //返回arr的第一项
=> item = {name: "moe", age: 40}
//使用chain() 方法
var youngest = _.chain(stooges)
.sortBy(function(stooge){ return stooge.age; })
.map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
.first()
.value();
=> youngest = {name: "moe", age: 40}
使用chain() 方法更加的优雅,简单易懂,对于数据的操作一目了然。chain() 方法接受一个数据参数stooges,后面的方法类似于管道,数据stooges像水流一样在其中流通,各个方法对其进行修改操作(不会改变原数据sooges),最后调用value() 方法将数据返回出来。
原理
chain()方法的返回值
当调用了chain() 方法之后,传入的数据在underscore内部由属性_wrapped储存,同时在instance对象扩展一个 _chain属性,用来标识instance对象是否是链式调用。当对象上有 _chain属性,不管是方法map() 、firest() 等还是chain() ,他们返回的都是underscore对象,如果没有 _chain属性,这些方法返回的是执行回调函数之后的数据
var _ = function(obj) {
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
}
_.chain = function(obj) {
var instance = _(obj);
instance._chain = true;
return instance;
};
在函数式编程-underscore整体结构一文中,underscore内部有个mixin() 方法,用来将underscore对象上的方法挂在到其原型对象上
var ArrayProto = Array.prototype;
var push = ArrayProto.push;
//判断obj是否是函数
_.isFunction = function(obj) {
return typeof obj == 'function' || false;
};
//返回一个保存obj中的方法名称的数组
_.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
_.mixin = function(obj) {
_.each(_.functions(obj), function(name) { //name是underscore所拥有的所有方法名称(each,map...)
var func = _[name] = obj[name];
_.prototype[name] = function() { //给underscore原型上扩展each、map等方法
var args = [this._wrapped]; //this._wrapped是储存传入的数据,即第二种风格传入的数据
push.apply(args, arguments); //将数据跟传入的参数合并 [this.wrapped, ...arguments]
return func.apply(_, args); //执行underscore内部方法,即执行第一种风格方法
};
});
return _;
};
当调用underscore中map,each等方法时,会执行mixin() 方法里挂载原型的方法,name指的是对相应的方法名称map、each...
_.prototype[name] = function() {
var args = [this._wrapped]
push.apply(args, arguments)
return func.apply(_, args)
};
此方法的返回值决定了执行方法map、each... 的返回值,如果这里直接返回了func.apply(_, args),underscore每个方法map、each... 就都要判断下underscore对象是否有 _chain属性(判断是否链式调用),有则返回underscore对象,没有直接返回数据。这样做太过于繁琐,在underscore源码中,这里返回了一个辅助函数chainResult()
// Helper function to continue chaining intermediate results.
var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
_.prototype[name] = function() {
var args = [this._wrapped]
push.apply(args, arguments)
return chainResult(this, func.apply(_, args));
};
chainResult() 接受两个参数,在这里instance指的是underscore对象,obj 指的是 _wrapped(传入的数据)。此方法判断当前underscore对象是否有 _chain属性。如果有,返回 _(obj).chain(),没有的话,直接返回数据 _wrapped。
_(obj).chain() 会返回什么呢?将其拆分成两部分, _(obj)以及.chain()。
- _(obj) 返回underscore实例对象,该对象属性 _wrapped等于传入的obj(数据)。
- 返回的实例对象继续调用 .chain(),会执行mixin() 方法里挂载原型的方法 _.prototype[name],这时的name就是chain了,调用该方法会执行 _.chain,返回underscore实例对象,该对象 _chain属性为true
最终_.chain()会返回underscore实例对象
_ {
_wrapped: "数据",
_chain: true
}
拿到underscore对象之后便可以继续调用underscore其他方法,并且它的属性 _chain始终为true
value()
_.prototype.value = function() {
return this._wrapped;
};
value的方法很简单,直接返回了 _wrapped,经过链式调用处理后的数据