underscore链式调用

486 阅读3分钟

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()。

  1. _(obj) 返回underscore实例对象,该对象属性 _wrapped等于传入的obj(数据)。
  2. 返回的实例对象继续调用 .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,经过链式调用处理后的数据