Skip to content

12. 万物皆可 reduce

要是有人问我 JS 中最喜欢的一个内置方法,那我的回答必然是是 Array.prototype.reduce

遇事不决,reduce 一把梭(开玩笑hh,还是看应用场景),不过本文讲的是 reduce 的各种用法。理论上,reduce 也可以代替很多其他常用方法,包括 map、filter 等等。

关于 reduce 的语法,请移步 MDN reduce

1. 求和

js
/**
 * @param  {...number} args
 * @returns {number}
 */
const sum = (...args) => 
  args.reduce((preSum, curNum) => preSum + curNum, 0)

sum(1, 2, 3) // 6
1
2
3
4
5
6
7
8

2. 计算字符出现的次数

js
/**
 * @param {string} s
 * @param {string} c
 * @returns {number}
 */
const count = (s, c) => 
  s.split('').reduce(
    (total, curChar) => total + (curChar === c ? 1 : 0),
    0
  )

count('ababbab', 'a') // 3
1
2
3
4
5
6
7
8
9
10
11
12

3. 统计字符

js
/**
 * @param {string} str
 * @returns {number}
 */
const countOf = str =>
  str.split('').reduce((preObj, curChar) => {
    preObj[curChar] = (preObj[curChar] ?? 0) + 1
    return preObj
  }, {})

countOf('abbcccdddde') // {a: 1, b: 2, c: 3, d: 4, e: 1}
1
2
3
4
5
6
7
8
9
10
11

4. 求最值

求最大值

js
/**
 * @param  {...number} nums
 * @returns {number}
 */
const max = (...nums) =>
  nums.reduce(
    (preMax, curNum) => (preMax < curNum ? curNum : preMax),
    -Infinity
  )

max(1, 5, 3, 2, 6, 4) // 6
max() // -Infinity
1
2
3
4
5
6
7
8
9
10
11
12

求最小值

js
/**
 *
 * @param {...number} nums
 * @returns {number}
 */
const min = (...nums) =>
  nums.reduce(
    (preMin, curNum) => (preMin > curNum ? curNum : preMin),
    Infinity
  )

min(1, 5, 3, 2, 6, 4) // 1
min() // Infinity
1
2
3
4
5
6
7
8
9
10
11
12
13

5. 数组扁平化

js
/**
 * @param {any[]} arr - 待拍平的数组
 * @param {number} depth -  要拍平的深度,默认为 1 层
 * @returns {any[]}
 */
const flatArr = (arr, depth = 1) => {
  return arr.reduce((pre, cur) => {
    if (Array.isArray(cur) && depth > 1) {
      return [...pre, ...flatArr(cur, depth - 1)]
    } else {
      return [...pre, cur]
    }
  }, [])
}

const arr = [1, [2, [3, 4, [5, 6], 7]]]
flatArr(arr)           // [ 1, 2, [ 3, 4, [ 5, 6 ], 7 ] ]
flatArr(arr, 1)        // [ 1, 2, [ 3, 4, [ 5, 6 ], 7 ] ]
flatArr(arr, 2)        // [ 1, 2, 3, 4, [ 5, 6 ], 7 ]
flatArr(arr, Infinity) // [ 1, 2, 3, 4, 5, 6, 7 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

6. 数组去重

js
/**
 * @param {number[]} nums
 * @returns {number[]}
 */
const deDup = nums => {
  return nums.reduce(
    (preArr, curNum) => 
      preArr.concat(preArr.includes(curNum) ? [] : [curNum]),
    []
  )
}

deDup([1, 2, 2, 3, 3, 3, 4, 5, 5]) //[ 1, 2, 3, 4, 5 ]
1
2
3
4
5
6
7
8
9
10
11
12
13

7. 插入排序

js
/**
 * @param {number[]} nums
 * @returns {number}
 */
const insertSort = nums => {
  return nums.reduce((pre, cur) => {
    let i = 0
    for (; i < pre.length; i++) {
      if (pre[i] > cur) break
    }
    return [...pre.slice(0, i), cur, ...pre.slice(i)]
    // return pre.slice(0, i).concat(cur).concat(pre.slice(i))
  }, [])
}

insertSort([4, -3, 11, 5, 7, 4n]) // [-3, 4, 4, 5, 7, 11]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

8. 合并对象数组

js
/**
 * @param {object[]} objArr
 * @returns {object}
 */
const merge = objArr =>
  objArr.reduce((pre, { key, value }) => {
    pre[key] = value
    return pre
  }, {})

const obj = [
  { key: 'name', value: 'tom' },
  { key: 'age', value: 20 },
  { key: 'sex', value: 'male' }
]
merge(obj) // { name: 'tom', age: 20, sex: 'male' }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

9. 实现 map 方法

reduce 能做的事情很多,甚至完全能够代替 map 方法。先来看一个例子:

js
/**
 * @param {number[]} nums
 * @returns {number[]}
 */
const squareOf = nums =>
  nums.reduce(
    (pre, cur) => [...pre, cur ** 2], []
  )

squareOf([1, 2, 3]) // [1, 4, 9]
1
2
3
4
5
6
7
8
9
10

上面这个例子等价于 arr.map(x => x ** 2)

下面是 reduce 方法实现的 map 方法,更加说明 reduce 可以完全代替 map:

js
/**
 * @param {(value: any, index: number, array: any[]) => any} callback
 * @param {any} thisArg
 * @returns {any[]}
 */
Array.prototype.reduceMap = function (callback, thisArg) {
  return this.reduce((pre, cur, index, arr) => {
    cur = callback.call(thisArg, cur, index, arr)
    pre.push(cur)
    return pre
  }, [])
}

let a = [1, 2, 3].reduceMap(function (x) {
  return x ** 2 + this.a
}, { a: 1 })
console.log(a) // [2, 5, 10],先平方,后+1
const double = [1, 2, 3].reduceMap(x => x * 2) // [2, 4, 6]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

10. 实现 filter 方法

js
/**
 * @param {(value: any, index: number, array: any[]) => any} callback
 * @param {any} thisArg
 * @returns {any[]}
 */
Array.prototype.reduceFilter = function (callback, thisArg) {
  return this.reduce(
    (pre, cur, index, arr) => {
      return pre.concat(
        callback.call(thisArg, cur, index, arr) ? cur : []
      )
    },
    []
  )
}

;[1, 2, 3, 4, 5, 6].reduceFilter(x => x & 1)
// [1, 3, 5],过滤出奇数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

总结

本文是总结 reduce 一些常用使用场景,旨在说明了这个方法的强大之处。但不代表每个场景中都要使用,比如该用 map、filter 的还是得用,怎么优雅怎么来。

有一个实验性的 Array.prototype.group(),浏览器暂时还不支持,但我们也可以使用 reduce 实现,这个就交给你了。

MDN:Array.prototype.group