引言
最近复习面试题的时候,看到 reduce() 这个方法的,便借此篇文章梳理一下 reduce() 。
概念
reduce() 是 JavaScript 中数组的一个内置方法,是一个迭代方法。MDN是这样定义的:
reduce()方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。
简而言之,就是 reduce() 方法对数组的每个元素运行一个 reducer 回调函数,得到一个值。
语法
reduce(callbackFn, [initialValue])
reduce((accumulator,currentValue,currentIndex,array)=>{}, [initialValue])
该 reduce 方法需要两个参数:
-
callbackFn 回调函数:是数组中每个元素执行的函数,其返回值将作为下一次调用callbackFn时的accumulator 参数,回调函数有四个参数:-
accumulator:必需值,表示上一次调用callbackFn的结果。在第一次调用时,如果指定了initialValue初始值,则该值为初始值,否则为arrary\[0]的值。 -
currentValue:必需值,表示当前元素的值。在第一次调用时,如果指定了initialValue的值,则为array\[0]的值,否则为array\[1]。 -
currentIndex:可选值,表示数组中的索引位置。在第一次调用时,如果指定了initialValue,则为 0,否则为 1 。 -
array:可选值,表示要处理的数组。
-
-
initialValue 初始值:可选参数,表示第一次调用回调函数时的初始化值。
例子
let arr = [15, 16, 17, 18, 19]
// 无初始值
let res1 = arr.reduce((pre, cur) => pre + cur);
// 有初始值
let res2 = arr.reduce((pre, cur) => pre + cur,10);
- 无初始值时,reduce()的回调函数被调用四次,数组索引为 0 的元素将被用作初始值,迭代器将从索引为 1的元素开始执行 。
- 有初始值时,reduce()的回调函数被调用五次,迭代器从索引为0的元素开始执行。
一些常见的用例
求加法/乘积
let arr = [1,2,3,4,5]
console.log(arr.reduce((a,b) => a + b))
console.log(arr.reduce((a,b) => a * b),2)
// 输出: 15 240
对象中的属性值求和
var arr = [
{
name: 'ww',
age: 19
},
{
name: 'xx',
age: 20
},
{
name: ' yy',
age: 21
}
];
var sum = arr.reduce((pre, cur) => {
return cur.age + pre;
}, 0);
console.log(sum)
// 输出: 60
求最大值(最小值)
// 求最大值
let arr = [23,123,342,12];
let max = arr.reduce((pre, cur) => {
return pre > cur ? pre : cur
});
console.log('max:',max);
//求最小值
let min = arr.reduce((pre, cur)=>{
return pre < cur ? pre : cur
})
console.log('min:',min);
// 输出: max: 342 min: 12
数组去重
let arr = [3,4,2,4,3,1,6]
let result = arr.reduce((pre, cur) => {
if (!pre.includes(cur)) {
pre.push(cur)
}
return pre
}, [])
console.log(result)
// 输出:[3,4,2,1,6]
统计某个元素(字符串、字符、单词等等)在数组中出现的次数
let arr = ["name","age","name","weight","weight","name","age"]
let result = arr.reduce((pre, cur)=> {
if (cur in pre){
pre[cur]++ // 若该元素在累加器里面存在,则设置将该元素的属性值+1
}
else{
pre[cur] = 1 // 若该元素在累加器里面没有,则设置该元素的属性值为1
}
return pre
},{})
console.log(result);
// 输出:{ name: 3, age: 2, weight: 2 }
// let b = {'e' : 1, '3' : 2}
// console.log(b['e']); 获取对象中属性值
// b['e'] = 3 设置对象中的属性值
// console.log(b['e']);
解析:
多维数组转化为一维(展平数组)
// 二维转一维
let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre,cur) => {
return pre.concat(cur)
},[])
console.log(newArr);
// 输出: [0, 1, 2, 3, 4, 5]
// 多维转一维
let arr2 = [[0, 1], [2,[3,4,5]], [6, 7]]
const newArr = function(arr){
var res = arr.reduce(function(pre,cur){
// console.log('cur',cur);
return pre.concat(Array.isArray(cur)?newArr(cur):cur)
},[])
return res
}
console.log(newArr(arr2));
// 输出: [0, 1, 2, 3, 4, 5, 6, 7]
解析:
按属性分组对象
let obj = [
{name: '李四', marks: 41},
{name: '张三', marks: 59},
{name: '王五', marks: 36},
{name: '孙六', marks: 90},
{name: '苏二', marks: 64},
];
let init = {
pass: [],
fail: []
}
let res = obj.reduce((pre, cur) => {
(cur.marks >= 50) ? pre.pass.push(cur) : pre.fail.push(cur);
return pre;
}, init);
console.log(res);
// 输出:
// {
// pass: [
// { name: '张三', marks: 59 },
// { name: '孙六', marks: 90 },
// { name: '苏二', marks: 64 }
// ],
// fail: [ { name: '李四', marks: 41 }, { name: '王五', marks: 36 } ]
// }
更多了解reduce()方法应用
reduce()
优缺点
优点
-
处理复杂逻辑:当需要对数组中的元素进行复杂的累积或组合操作时,
reduce()通过提供一个自定义的 reducer 函数,可以实现复杂的计算和数据转换。 -
初始值可定制:
reduce()方法接受一个初始值作为可选参数,这使得在处理数组时可以有一个明确的起点,有助于处理一些边界情况。 -
简洁性:相比于使用传统的循环结构(如
for循环或while循环),reduce()可以将多个步骤的操作合并为一个,代码量更少,代码更加紧凑。 -
例如:使用
filter()和map()会遍历数组两次,但是你可以使用reduce()只遍历一次并实现相同的效果,从而更高效。
缺点
-
复杂性:如果不仔细考虑,
reduce()可能会导致代码复杂且难以阅读。 -
调试:由于
reduce()它是一个高阶函数,因此由于其多步骤性质,调试错误可能更具挑战性。
何时避免使用reduce()
-
代码可读性:虽然
reduce()它是一个强大的工具,但它也可能导致复杂的代码。对于简单的数组转换或聚合,使用其他数组方法(例如map()或filter())可以使代码更具可读性和可维护性。 -
性能注意事项:对于需要执行简单的操作(例如查找数组中的最大值或最小值),使用特定方法(例如
Math.max()或Math.min())会比reduce()更有效。reduce()方法需要遍历整个数组,所以对于大型数组或性能敏感的应用,这可能会成为性能瓶颈。 -
错误处理:与循环结构不同,
reduce()方法不会因 reducer 函数中的错误而中断执行。这可能导致错误被忽略或难以调试。需要确保 reducer 函数能够妥善处理所有可能的错误情况。
最后
总的来说,reduce() 是一个强大的工具,可以利用它的强大功能简化复杂的数组操作,同时保持代码的可读性和效率。但并不总是最佳选择,在选择是否使用它时,应该考虑具体需求。