研究一下 javascripts 的 reduce ,reduce 是既能改变 array 的 size,又能改变数值的函数,filter 是只能改变size,不能改变数值;而 map 是不能改变 size,可以改变数值。很拗口吧,三个兄弟。
简单介绍一下 reduce。
假设我们有一个数组:
1[1, 2, 3, 4]
我们要对整个数组求和.
reduce
实际是按照下列的算式来进行求和的:
((((1) + 2) + 3) + 4)
那实际 reduce
函数执行中,你可以按你需求来自定义你自己的 + 操作符。数组的值也可以是其他的任意东西。听起来有点意思吧?
1、 Reduce 是干嘛的
在一个函数式编程语言中,reduce 其实有很多别的名称,比如 fold(对折), accumulate(累加器), aggregate(聚合器), compress(压缩) 甚至叫 inject(注入)。
2、 Reduce 的参数
常用用法如下:
1let myArray = [/* 首先定义一个数组 */];
2let callbackfn = /* 再定义一个函数 */ ;
3let initialvalue = /* 任意一个初始化的值 */ ;
4
5myArray.reduce(callbackfn)
6myArray.reduce(callbackfn, initialValue)
reduce
的参数如下:
callbackfn
: 必须是一个函数,会在整个数组中反复调用,reduce 调用 callbackfn 的时候有4个参数,我们定义它们是 previousValue
, currentElement
, index
和 array
,看起来像下面一样:
1callbackfn(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
initialValue
is present, 如果初始化变量不为空 - If
myArray
has no elements, 接着判断如果数组为空 - Return
initialValue
. 那么直接初始化变量作为 reduce 的结果返回 - else 初始化变量为空但数组不为空
- Let
accumulator
beinitialValue
. 把初始变量赋给累加器 - If the method is
reduce
, 如果方法是 reduce - Let
startIndex
be the index of the leftmost element ofmyArray
. 把数组最左边的元素的index值赋予 startIndex - else 如果初始化变量为空
- If
myArray
has no elements, 如果数组没有元素 - Throw
TypeError
. 初始化变量为空,数组也为空,直接抛类型错误 - Else if
myArray
has just only one element, 如果数组只有一个元素 - Return that element. 那么直接将数组中唯一一个元素作为 reduce 结果返回
- Else
- If the method is
reduce
,如果方法是 reduce - Let
accumulator
be the leftmost element ofmyArray
. 把数组最左边的第一个元素赋给累加器 - If the method is
reduce
, 如果方法是reduce - In left to right order, for each element of
myArray
such that its indexi
≥startingIndex
, 按照从左到右的顺序,遍历数组中的每一个元素,来个大循环 - Set
accumulator
tocallbackfn(accumulator, myArray[i], i, myArray)
. 数组中的每个元素,都逐个设置到callbackfn函数中并运行 - Return
accumulator
. 累加器的值作为 reduce 结果返回
仔细理解,搞完了吧。
给个实际例子,一群学生,有男有女,先选出女学生,然后计算出她们每个人的平均成绩,最后把她们打印出来:
1const students = [
2 {
3 name: "Anna",
4 sex: "f",
5 grades: [4.5, 3.5, 4]
6 },
7
8 {
9 name: "Dennis",
10 sex: "m",
11 country: "Germany",
12 grades: [5, 1.5, 4]
13 },
14
15 {
16 name: "Martha",
17 sex: "f",
18 grades: [5, 4, 2.5, 3]
19 },
20
21 {
22 name: "Brock",
23 sex: "m",
24 grades: [4, 3, 2]
25 }
26];
27
28
29//TODO: Compute and Return female students results using functional programming.
30
31function studentResult(students){
32 return students.filter(x => x.sex=="f").reduce((init,cur)=>{
33 cur['grades']=cur.grades.reduce((acc,cur) => acc+cur,0)/cur.grades.length;
34 return init.concat(cur);
35 },[]);
36}
37
38console.log(studentResult(students));
39//[ { name: 'Anna', sex: 'f', grades: 4 }, { name: 'Martha', sex: 'f', grades: 3.625 } ]
注意一点,reduce 可以动 size,也可以动值,上面实际改变了 students 数组元素的值了,不太好。应该赋一个新值 concat 或者 push 进新数组。
下面的方法就没有动原数组的数据:
1function studentResult(students){
2 return students.filter(x => x.sex=="f").reduce((init,cur)=>{
3 let newarr = {};
4 newarr['name']=cur.name;
5 newarr['sex']=cur.sex;
6 newarr['grades']=cur.grades.reduce((acc,cur) => acc+cur,0)/cur.grades.length;
7 init.push(newarr);
8 return init;
9 },[]);
10}