学习underscore之reduce

155 阅读3分钟

酝酿了很久,才开始动笔写自己的第一篇文章,有错误的地方还请指正,虽然很简单,但是代表了我学习reduce的思路,下面是正文

第一版

我们在调用reduce的时候是这样

var arr = [1,2,3,4,5]
var sum = reduce(arr, function(accumulator, value, index, collection) {
    return accumulator + value
}, 0)
// 15

underscore中有reduce也有reduceRight,只是遍历的方向不同,我们可以这样实现

function createReduce (dir) { // 传入方向参数
    // memo为初始值,在上个例子中也就是0
    return function (collection, iterator, memo, context) {
        var length = collection.length
        for (var i = 0; i < length; i++) {
            memo = iterator(memo, collection[i], i, collection)
        }
        return memo
    }
}
var reduce = createReduce(1) // 1 为正序
var reduceRight = createReduce(-1) // -1为倒序

是不是很简单,等等还有reduceRight呢,我们接着改

function createReduce (dir) { // 传入方向参数
    return function (collection, iterator, memo, context) {
        var length = collection.length
        var i = dir > 0 ? 0 : length - 1
        for (; i >= 0 && i < length; i += dir) {
            memo = iterator(memo, collection[i], i, collection)
        }
        return memo
    }
}

至此,我们完成了我们的第一版

第二版

reduce可以传入一个memo也可以不传,如果不传的话,默认以第一条作为memo。underscore中有一个优化了interator的方法叫做optimizeCb,它是长这个样子的,其实它做的工作就是根据参数数量返回了不同参数个数的函数而已,这里不做详细解释

function optimizeCb (func, context, argCount) {
  if (context === void 0) return func
  switch (argsCount) {
    case 1:
      return function (obj) {
        return func.call(context, obj)
      }
    case 2:
      return function (obj, value) {
        return func.call(context, obj, value)
      }
    case 3:
      return function (obj, value, collection) {
        return func.call(context, obj, value, collection)
      }
    case 4:
      return function (accumulator, obj, value, collection) {
        return func.call(context, accumulator, obj, value, collection)
      }
  }
  return function () {
    return func.apply(context, arguments)
  }
}

在我们的createReduce函数中这样改

function createReduce (dir) {
  function reduce (collection, iterator, memo, initial) {
    var length = collection.length
    var i = dir > 0 ? 0 : length - 1
    // 如果没有传入memo,以第一条作为memo,i向前一步
    if (!initial) {
      memo = collection[i]
      i += dir
    }
    for (; i >= 0 && i < length; i += dir) {
      memo = iterator(memo, collection[i], i, collection)
    }
    return memo
  }

  return function (collection, iterator, memo, context) {
    var initial = arguments.length >= 3 // 大于等于3 代表是传了memo
    return reduce(collection, optimizeCb(iterator, context, 4), memo, initial)
  }
}
第三版

除了可以传入数组,underscore也支持传入对象,使用方法是这样

_.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {// value 指值 key 指键
  (result[value] || (result[value] = [])).push(key);
  return result;
}, {})

// { '1': ['a', 'c'], '2': ['b'] } (无法保证遍历的顺序)

继续改我们的代码

function createReduce (dir) {
  function reduce (obj, iterator, memo, initial) {
    // 如果是数组keys -> false, 如果是对象返回键组成的数组
    var keys = !Array.isArray(obj) && Object.keys(obj)
    // 如果是数组返回数组的长度,否则返回keys的长度
    var length = (keys || obj).length
    var i = dir > 0 ? 0 : length - 1
    if (!initial) {
      // 如果是数组根据索引取memo,如果是对象根据属性名值取memo
      memo = obj[keys ? keys[i] : i]
      i += dir
    }
    for (; i >= 0 && i < length; i += dir) {
      // 如果是数组currentKey就是索引,如果是对象currentKey是属性名
      const currentKey = keys ? keys[i] : i
      memo = iterator(memo, obj[currentKey], i, obj)
    }
    return memo
  }

  return function (obj, iterator, memo, context) {
    var initial = arguments.length >= 3
    return reduce(obj, optimizeCb(iterator, context, 4), memo, initial)
  }
}

现在就完成了一个基本款的reduce方法

以下是underscore中的源码,供大家参考

var createReduce = function(dir) {
    // Wrap code that reassigns argument variables in a separate function than
    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
    var reducer = function(obj, iteratee, memo, initial) {
      var keys = !isArrayLike(obj) && _.keys(obj),
          length = (keys || obj).length,
          index = dir > 0 ? 0 : length - 1;
      if (!initial) {
        memo = obj[keys ? keys[index] : index];
        index += dir;
      }
      for (; index >= 0 && index < length; index += dir) {
        var currentKey = keys ? keys[index] : index;
        memo = iteratee(memo, obj[currentKey], currentKey, obj);
      }
      return memo;
    };

    return function(obj, iteratee, memo, context) {
      var initial = arguments.length >= 3;
      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
    };
};
 
_.reduce = _.foldl = _.inject = createReduce(1);
_.reduceRight = _.foldr = createReduce(-1)