Array.prototype.reduce 按顺序对每个元素跑你提供的回调,每一次都把「到目前为止的汇总结果」传进去,最后收成一个返回值。求和、摊平、计数、去重,都可以写成一次 reduce。
签名是 reduce(callback, initValue?)。回调参数依次为 previousValue、currentValue、currentIndex、arr。initValue 可选,决定第一轮的起点。
一、有没有初始值
| 是否传入 initValue |
previousValue 起点 |
currentValue 起点 |
currentIndex 起点 |
| 有 |
就是 initValue |
arr[0] |
0 |
| 无 |
arr[0] |
arr[1] |
1 |
空数组且不给初始值会抛 TypeError。有初始值时,空数组直接返回该初始值。
二、先看遍历顺序
有初始值 0 时,第一轮 previous 是 0,current 从 4 起:
const test1 = () => {
let arr = [4, 6, 7, 9]
arr.reduce((previousValue, currentValue, currentIndex) => {
console.log(`previousValue:${previousValue}, currentValue:${currentValue}, currentIndex:${currentIndex}`)
return currentValue
}, 0)
}
不给初始值时,第一轮 previous 直接是 4,current 从 6 起:
const test2 = () => {
let arr = [4, 6, 7, 9]
arr.reduce((previousValue, currentValue, currentIndex) => {
console.log(`previousValue:${previousValue}, currentValue:${currentValue}, currentIndex:${currentIndex}`)
return currentValue
})
}
三、四个典型用法
累加
const test3 = () => {
let arr = [4, 6, 7, 9]
let sum = arr.reduce((previousValue, currentValue, currentIndex) => {
console.log(previousValue) // 0,4,10,17
return previousValue + currentValue // 4,10,17,26
}, 0)
console.log(sum)
}
对象数组同样可以,把 score 加到累加器上:
const test4 = () => {
let arr = [
{ name: 'jack', score: 98 },
{ name: 'rose', score: 88 },
{ name: 'tom', score: 90 },
]
let total = arr.reduce((previousValue, currentValue) => currentValue.score + previousValue, 0)
console.log(total)
}
二维摊成一维
const test5 = () => {
const arr = [
[1, 2],
[3, 4],
[5, 6],
]
const newArr = arr.reduce((previousValue, currentValue) => [...previousValue, ...currentValue], [])
console.log(newArr)
}
统计出现次数
把字符串拆成字符数组,累加器是对象。用 in 判断键是否已存在:
const test6 = () => {
const str = 'helloworldjavascript'
const arr = str.split('')
const obj = arr.reduce(
(previousValue, currentValue) => {
currentValue in previousValue ? previousValue[currentValue]++ : (previousValue[currentValue] = 1)
return previousValue
}, {})
console.log(obj)
}
去重
const test7 = () => {
const arr = [1, 2, 3, 1, 2, 4, 5, 3]
const newArr = arr.reduce((previousValue, currentValue) => {
if (previousValue.indexOf(currentValue) == -1) {
previousValue.push(currentValue)
}
return previousValue
}, [])
console.log(newArr)
}
四、自己实现一个 reduce
先用 Object.prototype.toString.call 分清 Function / Array / 其它类型,再按「有无初始值」决定起点下标:
const test8 = () => {
let callback = null // [object Null]
let value = Object.prototype.toString.call(callback)
console.log(value)
}
Array.prototype.myReduce = function (callback, init) {
if (Object.prototype.toString.call(callback) !== '[object Function]') {
throw new TypeError('callback is not Function')
}
if (Object.prototype.toString.call(this) !== '[object Array]') {
throw new TypeError('not Array')
}
if (this.length === 0 && init === undefined) {
throw new TypeError('Reduce of empty arr with no initial value')
}
let index = init === undefined ? 1 : 0
let previousValue = init === undefined ? this[0] : init
const len = this.length
while (index < len) {
const currentValue = this[index]
previousValue = callback(previousValue, currentValue, index, this)
index++
}
return previousValue
}
原文用 !callback && toString !== Function 判断,空函数对象会漏检;这里改成只认 [object Function]。空数组错误信息里的 inital 也改成了 initial。
一句话总结:reduce 就是带累加器的遍历:先定初始值,再让每一轮的返回值成为下一轮的 previousValue,最后得到一个结果。