一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
作为前端项目中最常操作的数据类型之一,数组对象的方法有很多,包括遍历、过滤、添加、删除等。本文选取数组的reduce方法,并通过多个案例展开描述。
Reduce
reduce作为ES5中新增的方法,总的来说,reduce方法会对数组中的每一个元素执行传入的函数,并将函数运行后的结果作为参数,传入到下一个函数执行,直到数组中的元素执行完毕,最后将其结果汇总后返回。reduce与forEach、map等方法不同,虽然其他方法也有遍历,也可以操作数组内的元素,并返回结果,但是reduce返回的是“累加”后的结果。
语法
reduce是数组对象的一个原型方法,后续文章中涉及的均指数组。
reduce(callbackFunc, initialValue); // 简写
reduce((previousValue, currentValue, currentIndex, array) => {}, initialValue); // 完整的箭头函数写法
参数
initialValue:初始值,为可选参数。对应第一次调用callbackFunc函数时,其参数previousValue的值。如果指定了initialValue,则previousValue的值则为initialValue,此时currentValue的值为数组的第一个数据;如果未指定initialValue,则previousValue的值为数组的第一个,此时currentValue的值为数组的第二个。
需要注意:
- 如果数组为空,且未指定初始值initialValue,则会抛出Reduce of empty array with no initial value
- 如果数组仅有一个元素且未提供初始值,或者数组为空但是提供了初始值,则直接返回这个值并且不会执行callbackFunc。
- 建议始终提供初始值
let arr = [];
arr.reduce(() => {}); // 空数组调用reduce,并且未指定初始值 ---> 报错
arr.reduce(() => {}, 0); // 返回值为0
callbackFunc:回调函数,也可叫“累加”函数,必传参数。callbackFunc一共有4个参数,previousValue、currentValue、currentIndex、array。
- previousValue:上一次调用callbackFunc的返回值。第一次调用时,若指定了初始值,则为初始值,如果未指定初始值,则为数组的第一个元素。
- currentValue:callbackFunc中正在处理的元素。如果指定了初始值,则为数组第一个元素,否则为数组第二个元素。
- currentIndex:当前值在数组中的索引。如果指定了初始值,则索引从0开始,否则从1开始。
- array:当前数组对象本身。
callbackFunc函数的4个参数中,除了array,其他三个都与初始值有一定的关系。
执行案例
reduce的实际案例很多,如下挑选一些可能常用的:
累加
数组求和
let arr = [1,2,3];
console.log(arr.reduce((pre,cur) => (pre + cur), 0)); // 6
数组阶乘
let arr = [1,2,3,4];
console.log(arr.reduce((pre,cur) => (pre * cur), 1)); // 24
对象求和
let arr = [
{x:10,y:20},
{x:30,y:20},
{x:50,y:20},
];
console.log(arr.reduce((pre,cur) => (pre + cur.x), 0)); // 90
去除对象中所有的null、undefined属性
let obj = {
x: null,
y: undefined,
z: 'hahah'
}
console.log(Object.entries(obj).reduce((a, [k, v]) => {
if (v !== null && v !== undefined) {
a[k] = v
}
return a;
}, {})) // {z: 'hahah'}
根据对象的key排序
对象本身是无序的,可先将key值排序,然后根据有序的key重新创建对象并赋值
let obj = {
white: '#ffffff',
black: '#000000',
red: '#ff0000'
}
console.log(Object.keys(obj).sort().reduce((res, key) => {
res[key] = obj[key];
return res;
}, {})); // black、red、white
对象数组根据指定属性的值分组
let groupBy = (arr, key) => arr.reduce((pre, cur) => {
pre[cur[key]] = [...(pre[cur[key]] || []), cur];
return pre;
}, {});
groupBy(
[
{ branch: 'audi', model: 'q8', year: '2019' },
{ branch: 'audi', model: 'rs7', year: '2020' },
{ branch: 'ford', model: 'mustang', year: '2019' }
],
'branch'
); // audi、ford
展开一个多层嵌套的数组
展开数组可用reduce,也可用flat方法实现,但是flat方法有版本要求。
let flat = (arr) => arr.reduce((a, b) => (Array.isArray(b) ? [...a, ...flat(b)] : [...a, b]), []);
// flag = (arr) => arr.flat(); // 直接访问数组的flat方法,需考虑兼容性
flat(['cat', ['lion', 'tiger']]); // ['cat', 'lion', 'tiger']
找出数组中最接近指定值的索引
let closestNumIndex = (arr, num) => arr.reduce((pre, cur, index) => {
return [...pre, {'abs': Math.abs(cur - num), index}]; // 保存index
}, []).sort((a, b) => a.abs - b.abs)[0].index;
closestNumIndex([10, 5, 7, 45, 89, 65, 100], 50); // 3
统计数组对象中,指定属性的值出现的次数
- 首先reduce的初始值为{}空对象
- 动态添加或修改对象的属性,其中++undefined返回值为NAN,也就是说未出现的属性值赋值为1,否则自增1
- 返回当前对象便于下一次遍历时使用
let countBy = (arr, prop) => arr.reduce((pre, cur) => {
pre[cur[prop]] = ++pre[cur[prop]] || 1;
return pre;
}, {});
countBy([
{id: 1, value: 'a'},
{id: 1, value: 'b'},
{id: 2, value: 'c'}
], 'id'); // {1: 2, 2: 1}
总结
reduce在处理“累积”问题时有自己的优势,虽然用其他的方法也可以解决,但是使用reducce无疑会更简洁,特别是结合箭头函数,简单的问题一行代码就可以解决。处理数组、对象时多想想可不可以使用reduce解决,需要注意的是reduce最好指定初始值。
原创不易,转载请注明出处。