我们在业务开发中,经常要处理数组,转换为我们需要的数据。所以我们会经常使用数组实例方法,常用到的有 forEach、map、filter 等,它们符合单一职责原则,简单易懂,适合初学者。
而 reduce 方法,就没这么好理解了,但是它更加灵活,适合老鸟写一些花里胡哨的代码。
今天西瓜哥我就聊聊 reduce。
1
reduce 是什么?
Array.prototype.reduce 的用法是:迭代执行回调函数,并将回调函数返回的值传入给下一轮迭代。取最后一个迭代返回的值,作为 reduce 的返回值。
reduce 这个词又该如何理解?reduce 最常见的意思是 “减少”。根据用法,可能更适合翻译为 “折叠”。reduce 方法将每次迭代产生的结果带到下一次迭代中,最后将二维的线(数组)折叠为一维的点。虽然这个返回值可能也是数组,但从返回值这个维度,还是算作点。
继续详细讲解 reduce 的用法。
reduce 接收两个参数。
第一个参数是回调函数 reducer。
reducer 回调函数会接受由 reduce 提供的 4 个参数:
- accumulator:累加器,为上一个迭代执行的回调函数的返回值呀
- currentValue:当前迭代的数组元素的值
- index:当前迭代的数组元素的索引
- array:源数组
reducer 返回的值会在下一次迭代中传入到回调函数中。
第二个参数是初始值,是可选的。
如果不提供初始值,reduce 会将首元素作为累加器初始值,并从第二个数组元素开始迭代。
如果提供初始值,reduce 会将其作为累加器初始值,并从首数组元素开始迭代。
需要注意的是,数组为空的情况下,不提供初始值,会报类型错误。建议尽量提供初始值,以应对空数组的特殊情况。
Uncaught TypeError: Reduce of empty array with no initial value
2
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 也是可以作为默认值传入的。
3
reduce 的一些用法
reduce 最常见的用法是对数组求和,写法如下:
const arr = [1, 2, 3];const sum = arr.reduce((sum, cur) => sum + cur, 0); // 6
为了兼容空数组的情况,建议加上初始值 0。
在 ES5 时代,reduce 还能做下面的事情:
- 拍平二维数组,现在用 Array.prototype.flat;
- 数组去重,现在用 Array.from(new Set(arr));
- ...
其实说起来这些实现,forEach 也能实现。比如求和。
const arr = [1, 2, 3];let sum = 0;arr.forEach(val => { sum += cur });
但是 reduce 优点是不需要在方法外部声明一个变量,要更优雅一些。
而且如果 sum 变量名要修改了,reduce 函数里也不需要跟着改。
4
reduce 为什么灵活?
说 reduce 灵活,是因为它返回的值可以不是数组,可以是任何类型。比如我们你想要将数组通过特定规则转换为对象,reduce 就非常合适。
这代表了 reduce 是一个能灵活返回任意类型值的迭代器。
它能实现 forEach、map、filter、find 这些功能单一的方法,当然这没必要,但它确实可以。