Skip to content

数组的高阶函数 Array.prototype.reduce #225

@tofrankie

Description

@tofrankie

最近在写 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 或者数组的第一项(结合上面的理解)。千万不要被它的命名吓到,如果真是这样,就把它当做参数 abc 看待,因为它只是一个存储之前执行结果的变量而已。

  • currentValue:当前正在遍历的数组元素。

  • index(可选):当前正在遍历的数组元素的索引。若提供了 initialValue,则起始索引是 0,否则从索引 1 起始。

  • array(可选):调用 reduce() 的数组

三、MDN 官方描述

reduce 为数组中的每一个元素依次执行 reducer 函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:

  • accumulator 累计器
  • currentValue 当前值
  • currentIndex 当前索引
  • array 数组

回调函数第一次执行时,accumulator 和 currentValue 的取值有两种情况:

  1. 如果调用 reduce() 时提供了 initialValueaccumulator 取值为 initialValuecurrentValue 取数组中的第一个值;
  2. 如果没有提供 initialValue,那么 accumulator 取数组中的第一个值,currentValue 取数组中的第二个值。

**注意:**如果没有提供 initialValuereduce 会从索引 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(空项)执不执行困扰,所以我就全列举出来了。

四、应用场景

未完待续...

Metadata

Metadata

Assignees

No one assigned

    Labels

    20202020 年撰写JS与 JavaScript、ECMAScript 相关的文章尚未完结未完成的、未完结的文章

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions