前言
Array.reduce()
知道这个方法很久了,但却很少使用它。
但是它非常强大,可以做很多事情,今天带大家掌握并使用它。
简介
reduce()
方法对数组中的每个元素按序 执行一个提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果 作为参数传入,最后将其结果汇总为单个返回值。
第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被用作初始值,迭代器将从第二个元素开始执行(即从索引为 1 而不是 0 的位置开始)。
语法
reduce(callbackFn)
reduce(callbackFn, initialValue)
我们主要来看看 callbackFn
的参数:
- accumulator:上一次调用
callbackFn
的结果。在第一次调用时,如果指定了initialValue
则为指定的值,否则为array[0]
的值。 - currentValue
- currentIndex
- array: 调用了
reduce()
的数组本身。
它最后会将 accumulator
作为返回值返回。
了解这些基本信息之后,我们来看看它可以做些什么?
使用
求和
值数组
const arr = [1,2,3,4,5]
let result = arr.reduce((acc, cur) => acc + cur)
console.log(result)
对象数组
const arr = [{ x: 1 }, { x: 2 }, { x: 3 }]
let result = arr.reduce((acc, cur) => acc + cur.x, 0)
console.log(result)
对象数组我们使用了初始值,是因为 arr[0]
无法作为 reduce 的初始值,我们必须得设置初始值。
有个小疑问,上面两个求和的 reduce 回调函数各执行了几次?
展平嵌套数组
用 reduce 来实现 Array.flat()
示例1:
const flattened = [
[0, 1],
[2, 3],
[4, 5],
].reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
// flattened 的值是 [0, 1, 2, 3, 4, 5]
示例2:
var arr = [0, 1, 2, [3, [4, 5], 6], 7, 8, 9];
function flat(arr) {
return arr.reduce((acc, current) => {
if(current instanceof Array) {
return [...acc, ...flat(current)]
} else {
return [...acc, current]
}
}, [])
}
console.log(flat(arr)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
示例3:
var arr = [0, 1, 2, [3, [4, 5], 6], 7, 8, 9];
function flat(arr, acc) {
acc = acc || []
for(let i = 0; i < arr.length; i++) {
if(arr[i] instanceof Array) {
acc = [...acc, ...flat(arr[i])]
} else {
acc = [...acc, arr[i]]
}
}
return acc
}
console.log(flat(arr)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
统计对象总值的出现次数
const names = ["Alice", "Bob", "Tiff", "Bruce", "Alice"];
const countedNames = names.reduce((allNames, name) => {
const currCount = allNames[name] ?? 0;
return {
...allNames,
[name]: currCount + 1,
};
}, {});
// countedNames 的值是:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }
初始值为 {} ,我们可以给它添加属性
通过上面三个示例,我们知道 reduce 可以返回任何类型的值,如 number,array,object。这跟我们设置的初始值息息相关。
按属性对 对象进行分组
const people = [
{ name: "Alice", age: 21 },
{ name: "Max", age: 20 },
{ name: "Jane", age: 20 },
];
function groupBy(arr, prop) {
return arr.reduce((arr, cur) => {
const key = cur[prop]
const curGroup = arr[key] ?? []
return {
...arr,
[key]: [...curGroup, cur]
}
}, {})
}
const groupedPeople = groupBy(people, "age");
console.log(groupedPeople);
// {
// '20': [ { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ],
// '21': [ { name: 'Alice', age: 21 } ]
// }
如果想要返回数组类型,可将初始值设为 []
按属性整合数组中的值
const friends = [
{
name: "Anna",
books: ["Bible", "Harry Potter"],
age: 21,
},
{
name: "Bob",
books: ["War and peace", "Romeo and Juliet"],
age: 26,
},
{
name: "Alice",
books: ["The Lord of the Rings", "The Shining"],
age: 18,
},
];
const getAllBy = (arr, prop, initialValue) => {
return arr.reduce(
(accumulator, currentValue) =>
currentValue[prop] instanceof Array
? [...accumulator, ...currentValue[prop]]
: [...accumulator, currentValue[prop]],
initialValue ?? []
);
};
const allbooks = getAllBy(friends, "books", ["Alphabet"]);
// [
// 'Alphabet',
// 'Bible',
// 'Harry Potter',
// 'War and peace',
// 'Romeo and Juliet',
// 'The Lord of the Rings',
// 'The Shining'
// ]
const allnames = getAllBy(friends, "name");
// [ 'Anna', 'Bob', 'Alice' ]
去重
const myArray = ["a", "b", "a", "b", "c", "e", "e", "c", "d", "d", "d", "d"];
const myArrayWithNoDuplicates = myArray.reduce((accumulator, currentValue) => {
if (!accumulator.includes(currentValue)) {
return [...accumulator, currentValue];
}
return accumulator;
}, []);
代替 .filter().map()
使用 filter()
和 map()
会遍历数组两次,但是你可以使用 reduce()
只遍历一次并实现相同的效果,从而更高效。
const numbers = [-5, 6, 2, 0];
const doubledPositiveNumbers = numbers.reduce((accumulator, currentValue) => {
if (currentValue > 0) {
const doubled = currentValue * 2;
return [...accumulator, doubled];
}
return accumulator;
}, []);
console.log(doubledPositiveNumbers); // [12, 4]
按顺序执行promise
function runPromiseInSequence(arr, input) {
return arr.reduce(
(promiseChain, currentFunction) => promiseChain.then(currentFunction),
Promise.resolve(input),
);
}
// Promise 函数 1
function p1(a) {
return new Promise((resolve, reject) => {
resolve(a * 5);
});
}
// Promise 函数 2
function p2(a) {
return new Promise((resolve, reject) => {
resolve(a * 2);
});
}
// 函数 3——将由 `.then()` 包装在已解决的 Promise 中
function f3(a) {
return a * 3;
}
// Promise 函数 4
function p4(a) {
return new Promise((resolve, reject) => {
resolve(a * 4);
});
}
const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10).then(console.log); // 1200
有点像:
new Promise((resolve) => {
resolve(1);
})
.then((res) => new Promise((resolve) => resolve(res + 2)))
.then((res) => new Promise((resolve) => resolve(res + 3)))
.then(res => console.log(res)) // 6
使用 reduce 可灵活安排promise的执行顺序。
使用函数组合实现管道
// 组合使用的构建块
const double = (x) => 2 * x;
const triple = (x) => 3 * x;
const quadruple = (x) => 4 * x;
// 函数组合,实现管道功能
const pipe =
(...functions) =>
(initialValue) =>
functions.reduce((acc, fn) => fn(acc), initialValue);
// 组合的函数,实现特定值的乘法
const multiply6 = pipe(double, triple);
const multiply9 = pipe(triple, triple);
const multiply16 = pipe(quadruple, quadruple);
const multiply24 = pipe(double, triple, quadruple);
// 用例
multiply6(6); // 36
multiply9(9); // 81
multiply16(16); // 256
multiply24(10); // 240
这个有点意思,像递归一样,却每次执行不同的函数。其实 reduce 本身就是一个强大的递归函数。管道的概念没使用过,今后看看能不能用它解决一些问题。
在稀疏数组中使用 reduce()
reduce()
会跳过稀疏数组中缺失的元素,但不会跳过 undefined
值。
console.log([1, 2, , 4].reduce((a, b) => a + b)); // 7
console.log([1, 2, undefined, 4].reduce((a, b) => a + b)); // NaN
在非数组对象上调用 reduce()
const arrayLike = {
length: 3,
0: 2,
1: 3,
2: 4,
};
console.log(Array.prototype.reduce.call(arrayLike, (x, y) => x + y));
// 9
遍历类数组对象时,遍历次数取决于 length,从下标 0 到 length - 1,其他下标不会读取。
const arrayLike = {
length: 3,
0: 2,
1: 3,
3: 4,
A: "test",
};
console.log(Array.prototype.reduce.call(arrayLike, (x, y) => x + y));
// 5
ployfill
Array.prototype.reduceSelf = function(cb, initailVal) {
let arr = this
let len = arr.length
let acc = initailVal
for (let i = 0; i < len; i++) {
if(acc === undefined) {
acc = arr[0]
continue
} else {
acc = cb(acc, arr[i], i)
}
}
return acc
}
有什么问题,可以留言告诉我。
总结
你还可以用 reduce 实现 斐波那契数列,它应用场景还是很多的。但值得注意的是:
- 使用参数
accumulator
时,尽量使用 展开语法(...)或 其他复制方法来创建新的数组 或 对象 作为累加器。 - reduce 没有初始值时,索引 0 作为初始值,从索引 1 开始遍历。
- reduce 是通过递归实现的,有时间写个 pollfill。
动手点个赞吧!