reduce 简介

reduce 定义

reduce 在 MDN 里面的定义如下

reduce() 方法对累加器和数组中的每个元素 (从左到右)应用一个函数,将其减少为单个值。

firends!!!

看起来有点绕,不过从代码上来看就好理解多了

1
2
3
let ary=[1,2,3,4,5]
// 数组求和的 reduce 写法
let sum= ary.reduce((acc,val)=>acc+val,0); // sum= 15

reduce 接受两个参数,一个回调函数,一个初始值上文中的 (acc,val)=>acc+val 就是回调函数,0 则是遍历的初始值

其中在回调函数中,会传入四个参数

  • accumulator 上一次调用回调返回的值,或者是提供的初始值(initialValue)
  • currentValue 数组中正在处理的元素
  • currentIndex 数据中正在处理的元素索引,如果提供了 initialValue ,从0开始;否则从1开始
  • array 调用 reduce 的数组

最终返回累计处理的结果

为何 reduce

与 forEach 相比起来,reduce 不同之处在于,回调的首参不是列表中的元素,而是上一个调用的结果,或者是我们提供的初始值。
至于为什么……

如果说 forEach 是对应最基础的一种循环调用

1
2
3
4
5
6
let ary=[1,2,4]
for (let i=1;i<ary.length;i++){
dosomething(ary[i],i);
}
ary.forEach(dosomething)

reduce 则对应另外一种我们更经常会写的,带有累加概念的循环

1
2
3
4
5
6
7
8
9
10
let ary=[1,2,4]
sum=0;
for (let i=1;i<ary.length;i++){
sum+=ary[i]
}
// forEach 内的函数不纯
ary.forEach(val=>sum+=val)
// 可以看到此处 acc 等价于 sum ,reduce 的第二位参数 等价于把 sum = 0 这一初始化作用
ary.reduce((acc,val)=>acc+val,0);

而事实上第二种循环我们使用的频率也十分的高,但是当我们想用 forEach 抽象第二个循环的时候就会遇到一个麻烦。

因为这个循环体内的逻辑是不独立的,我们实际上借助了一个外部变量保持循环的结果,在循环之间传递数据。

对于每个循环逻辑独立,最后也不返回数据的 forEach 调用,我们无法抽离出一个独立的函数。

因此,reduce 就是为了解决了这个问题,将需要保持一个外部变量的逻辑,抽像为每次循环中上次执行的结果,传入到遍历函数当中,求得累加的结果,很有点递归的意味。

有趣的 reduce

而实际上这样一个遍历的逻辑,即便我这样解释列一大段,看起来还是稍显怪异,不禁让人想问那么到底有什么实际用途呢。

这确实有些不好解释,因为这是一个高度的抽像。

不过藉由 reduce 的抽象,我们可以定义出 map,filter 甚至是 compose 等等许多高级概念。

接下来我们就展示一下reduce的有趣之处!

map

定义 map, map 接受一个函数与数组,将函数应用到每个数组元素上,并返回一个将函数的结果作为新数组元素的数组

1
const map=(fn,arr)=>arr.reduce((acc,item,index,ary)=>acc.concat([fn(item,index,ary)]),[])

对于 map,我们通过对当前的 item 调用 fn ,并将结果 contact 到上一次调用返回的数组中,从空数组开始最终累积出新的数组

filter

同样的我们也可以通过 reduce 写出 filter,原理上来说与map的是实现非常相似,仅仅只是多加了一下判断而已

1
const filter= (fn,arr)=> arr.reduce((newArr,item)=> fn(item)? newArr.concat([item]):newArr,[])

compose/flow

了解过 lodash 的人应该知道其中一个十分有用的函数 flow,他的作用在于接受一组函数,按顺序调用,且上一个函数的结果就是下一个函数的参数。
这样看起来是不是熟悉?

1
2
3
4
5
const flow = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1ThenDouble= flow(x=>x+1,x=>x*2);
add1ThenDouble(2) // ((2+1)*2) = 6

总结

希望这篇文章能让你开始感受到 reduce 的有趣之处,虽然比起 map 或者 forEach 等等概念上来说,理解起来没那么容易。

但这是一个非常有用的工具,可以衍生出许多有趣的东西,绝对值得你花一点时间去去了解。

参考

Reduce (Composing Software)