reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是es5新增的一个数组逐项处理的方法。
语法:
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
callback:执行数组中每个值的函数,包含四个参数
accumulator:累计器,累计回调的返回值。如何有设置initialValue,则该值为初始值,否则以数组的第一个元素作为初始值。所以,正在没有设置initialValue的空数组上调用reduce将报错
currentValue:当前正在处理的数组元素
currentIndex:当前正在处理的数组元素的索引 - 可选
array:调用reduce方法的数组 - 可选
initialValue:初始值 - 可选
注意:reduce对于空数组是不会执行callback的
下面通过栗子来验证一下:
1、首先验证一下空数组
const arr1 = []
arr1.reduce((a, b, c, d) => {console.log('a: ' + a, 'b: ' + b, 'c: ' + c, 'd: d)
})
对于一个空数组,并且没有给初始值的情况下,浏览器会抛出一个类型错误
给一个初始值,则不会执行该方法:
const arr1 = []
arr1.reduce((a, b, c, d) => {
console.log('a: ' + a, 'b: ' + b, 'c: ' + c, 'd: ' + d)
}, 10)
可以看到,reduce对于空数组是不会执行callback的。
在平常开发中,reduce可以完成的,for基本也可以做到。那么为什么还要 用reduce?在我看来,他更有阅读性,更优雅,更有逼格。比如一个简单的累加:
const arr1 = [1, 2, 3, 4, 5]
// for
let result1 = 0
for (let i = 0, len = arr1.length; i < len; i++) {
result1 += arr1[i]
}
// reduce
let result2 = arr1.reduce((a, b) => a + b)
console.log(result1, result2)
结果都是一样的,但作为一名程序猿,得有自己的信仰:能用一行代码解决的事情,坚决不用两行代码。
一般用reduce用的比较多的是用它来数组去重,转化数组,或者是对象属性求和。
先来看数组去重:
// 简单数组
const arr1 = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1]
let single = arr1.reduce((a, b) => {
if (!a.includes(b)) {
return a.concat(b)
} else {
return a
}
}, [])
// 复杂数组
let arr2 = [
{
value: 1,
name: '1'
},
{
value: 2,
name: '2'
},
{
value: 1,
name: '1'
}
]
let hash = {}
let complex = arr2.reduce((a, b) => { // hash[b.value] ? '' : hash[b.value] = true && a.push(b)
if (!hash[b.value]) {
hash[b.value] = true && a.push(b)
}
return a
}, [])
console.log(single)
console.log(complex)
结果可以看到,都达到了去重的目的。其实简单数组的去重一般用的是Array.from(new Set(arr1)) 一行代码搞定。而数组对象的去重就没办法用Set了,所以一般在处理数组对象的去重时才会用到reduce。上面注释的三元表达式其实也是可以的,只是如果项目中用了eslint,直接使用三元表达式会报错,所以改成了if语句,也比较好理解。解释一下这段代码的意思,定义一个空对象hash,reduce方法传入了一个空数组,所以初始值为arr2的第一个元素。下面就是对数组arr2的从右到左的逐项处理,相当于一个遍历,只有在hash[b.value]为false的情况下,才进行下一步操作。下一步操作就是给hash[b.value]赋值,并将b塞到a里面。这样,在下一次遇到同样的b.value时,hash[b.value]就会存在,判断条件就变成了false,不会执行下一步操作,从而达到去重的目的。至于这里的true && a.push(b)为什么不可以改成false && a.push(b),因为这里其实做了一个隐式转换,push方法返回的是数组的长度,用true的话返回的还是该长度值,如果改成false,返回的值就是false了,这样再下一次判断hash[b.value]的时候取到的值就是false,整个判断条件就会变成true,这样就达不到去重的目的了。当然,或许会有一个疑问,既然true在这里只是做一个隐式转换,并且最后的值还不变,为什么还要加这个true呢?这个问题也是困扰了我很久,最后我想,大概是更利于阅读,更优雅,更有逼格吧。
下面说一下转化数组:
// 二维数组
const arr3 = [[1, 2], [3, 4], [5, 6]]
let newArr = arr3.reduce((a, b) => {
return a.concat(b)
}, [])
console.log(newArr)
// 多维数组
const arr4 = [[1, 2], [3, 4], [5, 6, [7, 8]]]
const handleGetNewArr = function(arr){
return arr.reduce((a, b) => a.concat(
Array.isArray(b) ? handleGetNewArr(b) : b
), [])
}
console.log(handleGetNewArr(arr4))
二维数组的基本很好理解,就用了一个es5的方法concat,把二维数组里的每一项拼接起来就形成了一个新的一维数组了。
多维数组的除了concat,还用到了递归,判断需要处理的当前项,即b是否为数组,如果是则递归调用handleGetNewArr方法,否则直接拼接。
最后说一下对象属性求和:
const arr5 = [
{
name: '圆珠笔',
price: 10
},
{
name: '橡皮擦',
price: 20
},
{
name: '钢笔',
price: 30
}
]
const sum = arr5.reduce((a, b) => {
return a + b.price
}, 0)
console.log(sum) // 60
数组对象的属性求和基本就是这样,没什么难理解的地方。
总的来说,reduce能做的事,用其他的遍历方法,如for,map等基本也可以实现。所以平常用的比较多的还是用它来进行数组对象的去重。
当然,还有一个reduceRight的方法,reduce是从右到左逐项处理,reduceRight是从左到右逐项处理,这个基本上没用到。因为两者除了处理顺序的差别之外,没啥区别了。