JavaScript Guidebook

JavaScript 完全知识体系

Array.prototype.reduce

Array.prototype.reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。对空数组时不会执行回调函数。

语法

语法:

arr.reduce(callbackfn [, initialValue]);

类型声明

interface Array<T> {
reduce(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T
): T;
reduce(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: readonly T[]) => T,
initialValue: T
): T;
reduce<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U,
initialValue: U
): U;
}

参数说明:

参数说明类型
callbackfn回调函数,用于遍历数组成员时执行function
initialValue(可选)累加器初始值,用作第一个调用回调函数的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用将报错。any

callbackfn 函数的参数:

  • previousValue:累加器累加回调的返回值,它是上一次调用回调时返回的累积值,或 initialValue
  • currentValue:当前数组中处理的元素
  • index:数组中正处理的当前元素的索引
  • array:被调用的数组

返回值:

返回函数累计处理的结果。

方法说明

reduce() 方法为数组中的每一个元素依次执行 callback 回调函数,不包括数组中被删除或从未被赋值的元素。

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

  • 回调函数参数取值问题
    • 提供 initialValue,累加器 acc 取值为 initialValuecurrentValue 取数组中的第一个值
    • 没有提供 initialValue,累加器 acc 取数组中的第一个值作为初始值currentValue 取数组中的第二个值。
  • 回调函数调用问题
    • 如果提供 initialValue,从索引 0 开始执行回调函数。
    • 如果没有提供 initialValuereduce 会从索引 1 的地方开始执行回调函数,跳过第一个索引。
    • 如果数组为空且没有提供 initialValue,会抛出 TypeError
    • 如果数组仅有一个元素(无论位置如何)并且没有提供 initialValue, 或者有提供 initialValue 但是数组为空,那么此唯一值将被返回并且 callback 不会被执行。

假如运行下段代码:

[0, 1, 2, 3, 4].reduce((acc, val, index, arr) => acc + val);

回调函数被调用四次,每次调用的参数和返回值如下表所示。

callback 回调函数acc 累加器val 当前值index 当前索引arr返回值
first call011[0, 1, 2, 3, 4]1
second call122[0, 1, 2, 3, 4]3
third call333[0, 1, 2, 3, 4]6
fourth call644[0, 1, 2, 3, 4]10

reduce() 方法最终的返回值为 10。

如果你打算提供一个初始值作为 reduce 方法的第二个参数,以下是运行过程及结果。

[0, 1, 2, 3, 4].reduce((acc, val, index, arr) => accumulator + currentValue, 10);
callback 回调函数acc 累加器val 当前值index 当前索引arr返回值
first call1000[0, 1, 2, 3, 4]10
second call1011[0, 1, 2, 3, 4]11
third call1122[0, 1, 2, 3, 4]13
fourth call1333[0, 1, 2, 3, 4]16
fifth call1644[0, 1, 2, 3, 4]20

reduce() 方法最终的返回值为 20。

代码示例

  • 将数组转为对象
  • 展开更大的数组
  • 在一次遍历中进行两次计算
  • 将映射和过滤函数组合
  • 按顺序运行异步函数

聚合为数字

数组成员为数字类型时。

const res = [1, 2, 3, 4, 5].reduce((acc, item) => acc + item, 0);
console.log(res);
// 15

数组成员为对象类型时。

const arr = [{ total: 1 }, { total: 2 }, { total: 3 }, { total: 4 }, { total: 5 }];
const res = arr.reduce((acc, { total }) => acc + total, 0);
console.log(res);
// 15

聚合为字符串

将数组的每项转换为固定格式的字符串,每项直接以分号作为分隔。

const arr = [
{ key: 'foo', value: 1 },
{ key: 'bar', value: 2 },
{ key: 'baz', value: 3 },
];
const res = arr.reduce((acc, { key, value }) => acc + `${key}=${value}&`, '?');
console.log(res);
// "?foo=1&bar=2&baz=3&"

聚合为对象

只要目标是将数组聚合为唯一的元素时,都可以考虑使用 reduce

const arr = [
{ id: 1, type: 'a', name: 'foo' },
{ id: 2, type: 'b', name: 'bar' },
{ id: 3, type: 'c', name: 'baz' },
];
const res = arr.reduce((acc, { id, type, name }) => {
acc[id] = { type, name };
return acc;
}, {});
console.log(res);
// { 1: { name: 'foo', type: 'a'}, 2: { name: 'bar', type: 'b'}, { name: 'baz', type: 'c' }}

初始值的必要性

提供初始值通常更安全。

没有提供初始值。

const maxCallback = ( pre, current ) => Math.max( pre.x, current.x )
[{ x: 22}, { x: 42}].reduce(maxCallback)
// 42
[{ x: 22}].reduce(maxCallback)
// { x: 22 }
[].reduce(maxCallback)
// TypeError

提供初始值。

const maxCallback = ( max, current ) => Math,max( max, current )
[{ x: 22 }, { x: 42 }].map( el => el.x ).reduce( maxCallback2, -Infinity );

数组求和、求积和最大值

// 数组求和
const sum = [0, 1, 2, 3].reduce((acc, cur) => acc + cur, 0);
// 6
// 数组求积
const product = [1, 2, 3, 4, 5].reduce((a, b) => a * b, 1);
// 120
// 数组最大值
const max = [1, 2, 3, 4, 5].reduce((a, b) => (a > b ? a : b));
// 5

数组元素

找出长度最长的数组元素。

const findLongest = (entries) =>
entries.reduce((prev, cur) => (cur.length > prev.length ? cur : prev), '');
console.log(findLongest([1, 2, 3, 'ab', 4, 'bcd', 5, 6785, 4]));
// 'bcd'

二维数组扁平化

const arr = [
[0, 1],
[2, 3],
[4, 5],
];
const res = arr.reduce((a, b) => a.concat(b), []);
console.log(res);
// [0, 1, 2, 3, 4, 5]

你也可以写成箭头函数的形式:

var flattened = [
[0, 1],
[2, 3],
[4, 5],
].reduce((acc, cur) => acc.concat(cur), []);

计算数组成员次数

const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];
const countedNames = names.reduce((allNames, name) => {
if (name in allNames) {
allNames[name]++;
} else {
allNames[name] = 1;
}
return allNames;
}, {});
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

单次遍历多次计算

const arr = [0.3, 1.2, 3.4, 0.2, 3.2, 5.5, 0.4];
function reduceMaxMin(acc, value) {
reuturn {
min: Math.min(acc.min, value),
max: Math.max(acc.max, value)
}
}
const initMinMax = {
min: Number.MIN_VALUE,
max: Number.MAX_VALUE
}
const minMax = arr.reduce(reduceMaxMin, initMinMax);
console.log(minMax);
// { min: 0.2, max: 5.5}

兼容性代码

if (!Array.prototype.reduce) {
Object.defineProperty(Array.prototype, 'reduce', {
value: function (callback) {
if (this === null) {
throw new TypeError('Array.prototype.reduce called on null or undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
// 将数组对象化
const obj = Object(this);
const len = obj.length >>> 0;
let index = 0;
let accumulator;
// 处理累加器(也就是 reduce 方法第二个参数)
if (arguments.length >= 2) {
// 累加器
accumulator = arguments[1];
} else {
while (index < len && !(index in obj)) {
index++;
}
if (index >= len) {
throw new TypeError('Reduce of empty array with no initial value');
}
accumulator = obj[index++];
}
// 走有累加器的那种实现
while (index < len) {
if (index in obj) {
accumulator = callback(accumulator, obj[index], index, obj);
}
index++;
}
return accumulator;
},
});
}

参考资料