underscore源码学习2--cb与optimizeCb

221 阅读3分钟

我们去看underscore源码的时候会发现,特别多的地方出现了cb, optimizeCb,iteratee等函数,我一开始看到这些也很迷惑,感觉绕来绕去,但这些就是一个开源库的健壮基石。

OptimizeCb

先来看看最简单的一个,源码如下

var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      // 因为underscore没有2个参数的情况
      // The 2-argument case is omitted because we’re not using it.
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

其实这个函数看起来一大堆,作用就一个,就是为了避免使用arguments。 第三个参数argCount就是func用到的参数个数,然后用个function包装一下,里面将参数一个个分好传给原始func

为什么没有argCount为2的情况呢?

因为搜索源码里用到optimizeCb的地方就会发现,没有一个地方是穿2个参数的场景,所以将这种情况去掉了

为什么要不使用arguments呢?

其实就是为了提高一点性能,开源库就是喜欢加这种东西

cb

cb的代码看起来不多,主要内容就是对value做一些兼容性的处理,代码如下


  // An internal function to generate callbacks that can be applied to each
  // element in a collection, returning the desired result — either `identity`,
  // an arbitrary callback, a property matcher, or a property accessor.
  var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value) && !_.isArray(value)) return _.matcher(value);
    return _.property(value);
  };

我们看underscore的文档会发现,很多API的参数要求很宽松,既可以传function,也可以传Array或是string,其实背后往往都是cb作了处理。 我们看一下执行该方法时候的逻辑

1. 判断iteratee方法是否有被修改,如果被修改则调用它并返回

我们看内部定义的iteratee方法

  _.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

刚好就是调用cb,所以如果没有修改的话,不用再处理一次

2. 判断是否有传入value,如果没有传值,则返回自身的identity方法

  // Keep the identity function around for default iteratees.
  _.identity = function(value) {
    return value;
  };

其实就是你使用map,filter之类的循环方法时,没有传需遍历执行的`function``,那我就只能把值原封不动的返回给你了,所以用到了这个方法

3. 判断传入的value是不是一个function,是的话调用optimizeCb处理一下

就跟上面讲的一样,如果你传入的是一个最正常的function,就简单的使用optimizeCb方法处理一下,提升一下性能

4. 判断value是不是一个object,是的话调用matcher方法

  // 返回一个断言函数,这个断言函数来辨别数组元素是否匹配制定键值属性
  _.matcher = _.matches = function(attrs) {
    attrs = _.extendOwn({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
  };

这个方法会返回一个用来判断两个对象键值对是否匹配的方法

5. 如果都没有匹配到,则调用property并返回

一般走到这里的value都是string类型,而property就是取对应key值的方法

  _.property = function(path) {
    if (!_.isArray(path)) {
      return shallowProperty(path);
    }
    return function(obj) {
      return deepGet(obj, path);
    };
  };

这时候就相当于会遍历取值返回

结语

对于一般的业务项目,都有比较固定的场景,不会对于传参的各种可能情况做处理,但对于一个开源库来说,多写这类没有实际逻辑功能的支撑方法,对于提升代码的健壮性是很有作用的。 我们在一些大型项目中也可以借鉴参考