数组 reduce() 方法详解及使用技巧

一、认识 reduce

MDN 中定义:reduce() 方法为数组里的每个元素执行执行回调函数(callback),不包括数组中被删除或从未被赋值的元素。

语法如下:

arr.reduce(callback(accumulator, currentValue, index, array), initialValue)

// 回调函数(callback)将执行数组中每个值,它包含 4 个参数:

// accumulator:累计器,也叫 previousValue,它是上一次回调的返回值,或者是提供的 initialValue。
// currentValue:当前值,数组中当前被处理的元素。
// index:当前索引,如果提供了 initialValue,index 值从 0 开始,否则从 1 开始。
// array:源数组,即调用 reduce() 的数组。

回调函数第一次执行时

  • 如果提供了 initialValue,那么 accumulator 取值为 initialValuecurrentValue 取数组中的第一个值,迭代次数为 arr.length
  • 如果没有提供 initialValue,那么 accumulator 取数组中的第一个值,currentValue 取数组中的第二个值,迭代次数为 arr.length - 1

下面介绍几个 reduce() 方法的常见应用。

二、数组累加

数组元素的累加,设置初始值。

let sum = [1, 2, 3, 5].reduce((acc, cur) => {
 return acc + cur 
}, 0)

console.log(sum)

三、对象属性求和

对象里的属性求和,设置初始值。

let sum = [{x: 1}, {x: 3}, {x: 5}, {x: 6}].reduce((acc, cur) => {
  return acc + cur.x
}, 0)

console.log(sum)

四、计算数组中每个元素出现的次数

需要注意的是需要提供一个初始值 {} 来承接每次循环的返回值。

let names = ['alice', 'Bob', 'lee', 'mark', 'mark']
let nameNum = names.reduce((acc, cur) => {
    if (!acc[cur]) {
        acc[cur] = 1
    } else {
        acc[cur]++
    }
    return acc
}, {})

console.log(nameNum)

五、将二维数组转化为一维

let arr = [[0, 1], [2, 3], [4, 5]]
let newArr = arr.reduce((pre, cur) => {
    return pre.concat(cur)
}, [])

console.log(newArr);

六、将多维数组转化为一维

采用递归的思路处理。

const flattenArr = function (arr) {
  return arr.reduce((acc, cur) => {
    return acc.concat(Array.isArray(cur) ? flattenArr(cur) : cur)
  }, [])
}

let arr = [[0, 1], [2, 3], [4, [5, 6, 7]]]
let newArr = flattenArr(arr)
console.log(newArr)

七、Promise 顺序执行队列

我们都知道,Promise 具有 Promise.all().then() 的用法,all()表 示所有的操作均执行完成,再执行 then() 中的方法。

all() 中的函数,不是顺序执行,而是同时执行的,看下面代码的打印效果。

let promiseFn1 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('resolve 1:', new Date().toLocaleString())
      resolve()
    }, 1000)
  })
}

let promiseFn2 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('resolve 2:', new Date().toLocaleString())
      resolve()
    }, 1000)
  })
}

let promiseFn3 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('resolve 3:', new Date().toLocaleString())
      resolve('last resolve')
    }, 1000)
  })
}

let promiseArr = [promiseFn1(), promiseFn2(), promiseFn3()]
Promise.all(promiseArr).then(res => {
  console.log('res:', res)
})

从运行结果看来,他们几乎是同一时刻执行的。这种异步,虽然最终都会全部执行完成后,再执行 then() 中的代码,但是 all() 中的函数并没有顺序执行。

如果希望 then() 之前的函数都是顺序执行的,可以考虑通过 reduce() 方法来实现。这里会比较难以理解,看下面代码:

let promiseFn1 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('resolve 1')
      resolve()
    }, 1000)
  })
}

let promiseFn2 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('resolve 2')
      resolve()
    }, 1000)
  })
}

let promiseFn3 = function () {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('resolve 3')
      resolve('last resolve')
    }, 1000)
  })
}

let promiseArr = [promiseFn1, promiseFn2, promiseFn3]
let promiseArrTask = promiseArr.reduce((acc, cur) => {
  return acc.then(cur)
}, Promise.resolve()) // 提供initialValue,从第一个参数遍历

promiseArrTask.then(res => {
  console.log('res:', res)
})

可以看到,这里三个函数是按照顺序执行的,并没有同时执行,等到全部执行完成,最后执行 then() 中的方法。

其实这种写法,就相当于 Promise().then().then().then() 的写法, 只不过使用 reduce() 代码更简洁了。

我们需要注意的是 reduce() 的数组是一个 Function 的数组,而不是 Promise 的数组。另外,reduce() 方法的第二个参数,他是一个初始值,在这里就是 Promise.resolve(),他表示一个动作已经执行完成,可以进行下一个动作了。

八、替代部分其他数组高阶函数

1. reduce 替代 map

const arr = [{name: 'Amy'}, {name: 'Bob'}]
let newArr1 = arr.map(it => it.name) // map
let newArr2 = arr.reduce((acc, cur) => [...acc, cur.name], []) // reduce

console.log(newArr1)
console.log(newArr2)

2. reduce 替代 filter

const arr = [{name: 'Amy', age: 18}, {name: 'Bob', age: 20}]
let newArr1 = arr.filter(it => it.age > 18) // filter
let newArr2 = arr.reduce((c, n) => n.age > 18 ? [...c, n]:c, []) // reduce

console.log(newArr1)
console.log(newArr2)

3. reduce 替代 map + filter

const arr = [{name: 'Amy', age: 18}, {name: 'Bob', age: 20}];
let newArr1 = arr.filter(it => it.age > 18).map(it => it.name) // 多重循环效率低浪费大 
let newArr2 = arr.reduce((c, n) => n.age > 18 ? [...c, n.name] : c, []) // reduce   Bob

console.log(newArr1)
console.log(newArr2)

4. reduce 替代 some 或者 every

const arr = [{name: 'Amy', age: 18}, {name: 'Bob', age: 20}]
let some = arr.reduce((c, n) => c || n.age > 18, false) // some
let every = arr.reduce((c, n) => c && n.age > 18, true) // every

console.log(some)
console.log(every)

九、总结

关于 reduce() 方法的使用技巧暂时整理这么多,其余的可以在实际工作中再慢慢积累。


博文对你有帮助吗?如果有的话,想不想送我一本书呢?
  目录