数组的高阶函数Array.prototype.reduce
最近在写 React 系列文章,其中写到 Redux 的时候(这里),提到了一个叫做 compose
的高阶函数, 它里面用到的就是 Array.prototype.reduce 方法。它的源码如下:
// https://github.com/reduxjs/redux/blob/4.x/src/compose.js
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
其实类似的组合函数,很多都是使用到 Array.prototype.reduce
方法。在此之前,平时比较少用到它,就没好好整理过知识点,但最近我认为,这是必须掌握而且用起来的方法。
To be continue...
一、reduce 方法
它是 ES5 提供的一个数组原生的方法,语法如下:
arr.reduce(reducer, [initialValue])
参数
-
reducer:是执行数组中每个值(如果没有提供
initialValue
则第一个值除外)的函数。 -
initialValue(可选):作为第一次调用
reducer
函数时的第一个参数的值。如果没有提供初始值,则将使用数组中的第一个元素。在没有初始值的空数组上调用reduce
方法将报错。
返回值
函数累计处理的结果
const arr1 = [1, 2, 3, 4]
const arr2 = []
const arr3 = new Array(10)
const reducer = (acc, cur) => {
console.log(`acc: ${acc}, cur: ${cur}`)
return acc + cur
}
// (1) 是否含初始值的区别
const sum1 = arr1.reduce(reducer) // 循环三次(reduce没有初始值,数组第一项被作为 acc 的初始值)
const sum2 = arr1.reduce(reducer, 0) // 循环四次
// (2) 空数组访问 reduce 方法,必须传初始值,否则报错
const sum3 = arr2.reduce(reducer, 0) // 初始值 0 作为返回结果,且不执行 reducer
const sum4 = arr2.reduce(reducer) // 运行报错
// (3) 同理
const sum5 = arr3.reduce(reducer, 0) // 初始值 0 作为返回结果,从未被赋值的元素不执行 reducer
const sum6 = arr3.reduce(reducer) // 运行报错
console.log(sum1, sum2, sum3, sum4, sum5, sum6)
二、reducer 函数
arr.reduce(reducer(accumulator, currentValue, index, array), initialValue)
它有四个参数:
-
accumulator:累计器回调的返回值。它是上一次调用回调时返回的累计值。若它是第一次执行,它的值就是
initialValue
或者数组的第一项(结合上面的理解)。千万不要被它的命名吓到,如果真是这样,就把它当做参数a
或b
或c
看待,因为它只是一个存储之前执行结果的变量而已。 -
currentValue:当前正在遍历的数组元素。
-
index(可选):当前正在遍历的数组元素的索引。若提供了
initialValue
,则起始索引是0
,否则从索引1
起始。 -
array(可选):调用
reduce()
的数组
三、MDN 官方描述
reduce
为数组中的每一个元素依次执行 reducer
函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:
accumulator 累计器
currentValue 当前值
currentIndex 当前索引
array 数组
回调函数第一次执行时,accumulator
和 currentValue
的取值有两种情况:
- 如果调用
reduce()
时提供了initialValue
,accumulator
取值为initialValue
,currentValue
取数组中的第一个值; - 如果没有提供
initialValue
,那么accumulator
取数组中的第一个值,currentValue
取数组中的第二个值。
注意:如果没有提供
initialValue
,reduce
会从索引1
的地方开始执行reducer
方法,跳过第一个索引。如果提供initialValue
,从索引0
开始。
如果数组为空且没有提供 initialValue
,会抛出 TypeError
。如果数组仅有一个元素(无论位置如何)并且没有提供 initialValue
, 或者有提供 initialValue
但是数组为空,那么此唯一值将被返回并且 reducer
不会被执行。
*这一小节跟上面的是有重复的。
就本节内容,举例说明一下:
// 在 Array 原型上添加一个 delete 方法:用于删除数组某项
Array.prototype.delete = function (item) {
for (let i = 0; i < this.length; i++) {
if (this[i] === item) {
this.splice(i, 1)
return this
}
}
return this
}
const arr1 = new Array(10)
const arr2 = [1, 2, undefined, null]
const arr3 = [1, 2, 3, 4]
const reducer = (acc, cur, index, arr) => {
// 删除 arr3 数组的第二项
if (index === 0 && arr === arr3) arr.delete(2)
return `${acc} - ${cur}`
}
const sum1 = arr1.reduce(reducer, 0) // 返回:0
const sum2 = arr2.reduce(reducer, 0) // 返回:0 - 1 - 2 - undefined - null
const sum3 = arr3.reduce(reducer, 0) // 返回:0 - 1 - 3 - 4
// 结论:
// 1. 空项(未被赋值项)不执行 reducer ,而 undefined、null 会执行 reducer
// 2. 被删除项,不执行 reducer
// PS: 由于我经常被各种方法对 undefined、null、empty(空项)执不执行困扰,所以我就全列举出来了。
四、应用场景
未完待续...