酝酿了很久,才开始动笔写自己的第一篇文章,有错误的地方还请指正,虽然很简单,但是代表了我学习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)