研究一下 javascripts 的 reduce ,reduce 是既能改变 array 的 size,又能改变数值的函数,filter 是只能改变size,不能改变数值;而 map 是不能改变 size,可以改变数值。很拗口吧,三个兄弟。
简单介绍一下 reduce。
假设我们有一个数组:
[1, 2, 3, 4]
我们要对整个数组求和.
reduce 实际是按照下列的算式来进行求和的:
((((1) + 2) + 3) + 4)
那实际 reduce 函数执行中,你可以按你需求来自定义你自己的 + 操作符。数组的值也可以是其他的任意东西。听起来有点意思吧?
1、 Reduce 是干嘛的
在一个函数式编程语言中,reduce 其实有很多别的名称,比如 fold(对折), accumulate(累加器), aggregate(聚合器), compress(压缩) 甚至叫 inject(注入)。
2、 Reduce 的参数
常用用法如下:
let myArray = [/* 首先定义一个数组 */];
let callbackfn = /* 再定义一个函数 */ ;
let initialvalue = /* 任意一个初始化的值 */ ;
myArray.reduce(callbackfn)
myArray.reduce(callbackfn, initialValue)
reduce 的参数如下:
callbackfn: 必须是一个函数,会在整个数组中反复调用,reduce 调用 callbackfn 的时候有4个参数,我们定义它们是 previousValue, currentElement, index 和 array ,看起来像下面一样:
callbackfn(previousValue, currentElement, index, array)
解释一下:
previousValue: 这个参数就是一个累加器。currentElement: 数组中处理的当前元素。index: 当前元素的索引值。array:myArray调用的数组.
Return value(返回值): 最后一次调用 callbackfn 的时候,返回值就是整个 reduce 过程的返回值。如果不是最后一次调用,它返回的值会被下次的 callbackFn 的 previousvalue 参数接收。
我们必须注意,函数就是函数,函数过程中处理的变量要么是外围的 scope 带进来的,要么是函数体内自定义的,所以后面两个 index 和 array 也是必须存在的,只不过是 reduce 函数替你自动处理了。
3、 用画图来理解 Reduce

看上面的图,reduce 和 reductRight 函数的区别就是方向,一个是从左到右,一个是从右到左。
关注点如下:
acc相当于previousValue,累加器.curVal相当于currentElement,当前处理元素.- 数组中的每个元素向下输出到圈
r就是curVal输出到***r***的具体表现. - 包含数组元素的长方形输出到下一个
r就是acc输出到***r***的具体表现. - 初始化值在数组外单独表示,它是作为一个单独的
acc输出到r中的.
3、 用流程图来理解 Reduce
下面用20行的伪代码来详细解释整个 reduce 的过程,首先进入 reduce 函数:
- If
initialValueis present, 如果初始化变量不为空 - If
myArrayhas no elements, 接着判断如果数组为空 - Return
initialValue. 那么直接初始化变量作为 reduce 的结果返回 - else 初始化变量为空但数组不为空
- Let
accumulatorbeinitialValue. 把初始变量赋给累加器 - If the method is
reduce, 如果方法是 reduce - Let
startIndexbe the index of the leftmost element ofmyArray. 把数组最左边的元素的index值赋予 startIndex - else 如果初始化变量为空
- If
myArrayhas no elements, 如果数组没有元素 - Throw
TypeError. 初始化变量为空,数组也为空,直接抛类型错误 - Else if
myArrayhas just only one element, 如果数组只有一个元素 - Return that element. 那么直接将数组中唯一一个元素作为 reduce 结果返回
- Else
- If the method is
reduce,如果方法是 reduce - Let
accumulatorbe the leftmost element ofmyArray. 把数组最左边的第一个元素赋给累加器 - If the method is
reduce, 如果方法是reduce - In left to right order, for each element of
myArraysuch that its indexi≥startingIndex, 按照从左到右的顺序,遍历数组中的每一个元素,来个大循环 - Set
accumulatortocallbackfn(accumulator, myArray[i], i, myArray). 数组中的每个元素,都逐个设置到callbackfn函数中并运行 - Return
accumulator. 累加器的值作为 reduce 结果返回
仔细理解,搞完了吧。
给个实际例子,一群学生,有男有女,先选出女学生,然后计算出她们每个人的平均成绩,最后把她们打印出来:
const students = [
{
name: "Anna",
sex: "f",
grades: [4.5, 3.5, 4]
},
{
name: "Dennis",
sex: "m",
country: "Germany",
grades: [5, 1.5, 4]
},
{
name: "Martha",
sex: "f",
grades: [5, 4, 2.5, 3]
},
{
name: "Brock",
sex: "m",
grades: [4, 3, 2]
}
];
//TODO: Compute and Return female students results using functional programming.
function studentResult(students){
return students.filter(x => x.sex=="f").reduce((init,cur)=>{
cur['grades']=cur.grades.reduce((acc,cur) => acc+cur,0)/cur.grades.length;
return init.concat(cur);
},[]);
}
console.log(studentResult(students));
//[ { name: 'Anna', sex: 'f', grades: 4 }, { name: 'Martha', sex: 'f', grades: 3.625 } ]
注意一点,reduce 可以动 size,也可以动值,上面实际改变了 students 数组元素的值了,不太好。应该赋一个新值 concat 或者 push 进新数组。
下面的方法就没有动原数组的数据:
function studentResult(students){
return students.filter(x => x.sex=="f").reduce((init,cur)=>{
let newarr = {};
newarr['name']=cur.name;
newarr['sex']=cur.sex;
newarr['grades']=cur.grades.reduce((acc,cur) => acc+cur,0)/cur.grades.length;
init.push(newarr);
return init;
},[]);
}