likes
comments
collection
share

为什么说数组实例的 reduce 方法灵活?

作者站长头像
站长
· 阅读数 34

这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

我们在业务开发中,经常要处理数组,转换为我们需要的数据。所以我们会经常使用数组实例方法,常用到的有 forEach、map、filter 等,它们符合单一职责原则,简单易懂,适合初学者。

而 reduce 方法,就没这么好理解了,但是它更加灵活,适合老鸟写一些花里胡哨的代码。

今天西瓜哥我就聊聊 reduce。

Array.prototype.reduce 是什么?

Array.prototype.reduce 的用法是:迭代执行回调函数,并将回调函数返回的值传入给下一轮迭代。取最后一个迭代返回的值,作为 reduce 的返回值。

reduce 这个词又该如何理解?reduce 最常见的意思是 “减少”。根据用法,可能更适合翻译为 “折叠”。reduce 方法将每次迭代产生的结果带到下一次迭代中,最后将二维的线(数组)折叠为一维的点。虽然这个返回值可能也是数组,但从返回值这个维度,还是算作点。

继续详细讲解 reduce 的用法。

reduce 接收两个参数。

第一个参数是回调函数 reducer。

reducer 回调函数会接受由 reduce 提供的 4 个参数:

  1. accumulator:累加器,为上一个迭代执行的回调函数的返回值呀
  2. currentValue:当前迭代的数组元素的值
  3. index:当前迭代的数组元素的索引
  4. array:源数组

当前 reducer 返回的值会在下一次迭代中传入到回调函数中。

第二个参数是初始值,是可选的。

如果不提供初始值,reduce 会将首元素作为累加器初始值,并从第二个数组元素开始迭代。

如果提供初始值,reduce 会将其作为累加器初始值,并从首数组元素开始迭代。

需要注意的是,数组为空的情况下,不提供初始值,会报类型错误。 建议尽量提供初始值,以应对空数组的特殊情况。

Uncaught TypeError: Reduce of empty array with no initial value

reduce 的简单实现

要想真正理解这个神奇的 reduce 的运行机制,还得自己实现一下。这里我实现个简单的 reduce 方法:

Array.prototype.myReduce = function(callbackFn, initVal) {
  const arr = this;
  if (typeof callbackFn !== 'function') {
    throw new TypeError(callbackFn + ' is not a function');
  }
  let i = 0;
  let acc = initVal;
  if (arguments.length < 2) { // 没有提供初始值的情况
    if (arr.length === 0) {
      throw new TypeError('Reduce of empty array with no initial value');
    }
    acc = arr[0];
    i = 1;
  }

  for (; i < arr.length; i++) {
    acc = callbackFn(acc, arr[i], i, arr);
  }
  return acc;
}

核心代码在于通过 arguments.length 判断是否有提供第二个参数(默认值参数)。

如果提供了,累加器变量设置为传入的默认值,迭代从索引 0 开始(i = 0);如果没有提供,累加器变量设置为首个数组元素,迭代从 1 开始。

不要用第二个参数是否为 undefind 来判断,因为 undefind 也是可以作为默认值传入的。

reduce 的一些用法

reduce 最常见的用法是对数组求和,写法如下:

const arr = [1, 2, 3];
const sum = arr.reduce((sum, cur) => sum + cur, 0); // 6

为了兼容空数组的情况,建议加上初始值 0。

在 ES5 时代,reduce 还能做下面的事情:

  1. 拍平二维数组,现在用 Array.prototype.flat
  2. 数组去重,现在用 Array.from(new Set(arr))
  3. ...

其实说起来,这些实现 forEach 也能实现。比如求和。

const arr = [1, 2, 3];
let sum = 0;
arr.forEach(val => { sum += cur }); 

但是 reduce 优点是不需要在方法外部声明一个变量,要更优雅一些。

而且如果 sum 变量名要修改了,reduce 函数里也不需要跟着改。

reduce 为什么灵活?

说 reduce 灵活,是因为它返回的值可以不是数组,可以是任何类型。 比如我们你想要将数组通过特定规则转换为对象,reduce 就非常合适。

这代表了 reduce 是一个能灵活返回任意类型值的迭代器

它能实现 forEach、map、filter、find 这些功能单一的方法,当然这没必要,但它确实可以。

本文首发于我的公众号:前端西瓜哥