Array.prototype.reduce()

Array.prototype.reduce()

reduce() 方法对数组中的每个元素按序执行一个由您提供的 reducer 函数,每一次运行 reducer 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被作为初始值 initialValue,迭代器将从第二个元素开始执行(索引为 1 而不是 0)。

下面的例子能够帮助你理解 reduce() 的用处——计算数组所有元素的总和:

尝试一下

reducer 逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果相加(上一步的计算结果是当前元素之前所有元素的总和)——直到没有更多的元素被相加。

语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Arrow function
reduce((previousValue, currentValue) => { /* ... */ } )
reduce((previousValue, currentValue, currentIndex) => { /* ... */ } )
reduce((previousValue, currentValue, currentIndex, array) => { /* ... */ } )
reduce((previousValue, currentValue, currentIndex, array) => { /* ... */ }, initialValue)
 
// Callback function
reduce(callbackFn)
reduce(callbackFn, initialValue)
 
// Inline callback function
reduce(function(previousValue, currentValue) { /* ... */ })
reduce(function(previousValue, currentValue, currentIndex) { /* ... */ })
reduce(function(previousValue, currentValue, currentIndex, array) { /* ... */ })
reduce(function(previousValue, currentValue, currentIndex, array) { /* ... */ }, initialValue)

参数

callbackFn

一个 “reducer” 函数,包含四个参数:

  • previousValue:上一次调用 callbackFn 时的返回值。在第一次调用时,若指定了初始值 initialValue,其值则为 initialValue,否则为数组索引为 0 的元素 array[0]
  • currentValue:数组中正在处理的元素。在第一次调用时,若指定了初始值 initialValue,其值则为数组索引为 0 的元素 array[0],否则为 array[1]
  • currentIndex:数组中正在处理的元素的索引。若指定了初始值 initialValue,则起始索引号为 0,否则从索引 1 起始。
  • array:用于遍历的数组。
initialValue 可选

作为第一次调用 callback 函数时参数 previousValue 的值。若指定了初始值 initialValue,则 currentValue 则将使用数组第一个元素;否则 previousValue 将使用数组第一个元素,而 currentValue 将使用数组第二个元素。

返回值

使用 “reducer” 回调函数遍历整个数组后的结果。

异常

TypeError

数组为空且初始值 initialValue 未提供。

描述

ECMAScript 规范描述了 reduce() 的行为:

callbackfn 应是一个接受四个参数的函数,reduce 对于数组中第一个元素之后的每一个元素,按升序各调用一次回调函数。

callbackfn 被调用时会传入四个参数:

  • previousValue(前一次调用 callbackfn 得到的返回值)
  • currentValue(数组中正在处理的元素)
  • currentIndex(数组中正在处理的元素的索引)
  • 被遍历的对象

回调函数第一次执行时,previousValue 和 currentValue 的取值有两种情况:

  • 如果调用 reduce() 时提供了 initialValuepreviousValue 取值则为 initialValuecurrentValue 则取数组中的第一个值。
  • 如果没有提供 initialValue,那么 previousValue 取数组中的第一个值,currentValue 取数组中的第二个值。

如果数组为空且未指定初始值 initialValue,则会抛出 TypeError

reduce 不会直接改变调用它的对象,但对象可被调用的 callbackfn 所改变。

遍历的元素范围是在第一次调用 callbackfn 之前确定的。所以即使有元素在调用开始后被追加到数组中,这些元素也不会被 callbackfn 访问。如果数组现有的元素发生了变化,传递给 callbackfn 的值将会是元素被 reduce 访问时的值(即发生变化后的值);在调用 reduce 开始后,尚未被访问的元素若被删除,则其将不会被 reduce 访问。

如果数组仅有一个元素(无论位置如何)并且没有提供初始值 initialValue,或者有提供 initialValue 但是数组为空,那么此唯一值将被返回且 callbackfn 不会被执行。

提供初始值 initialValue 通常更安全,正如下面的例子,如果没有提供 initialValue,则 reduce 方法会因数组长度的不同(大于 1、等于 1、等于 0)而有不同的表现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const getMax = (a, b) => Math.max(a, b);
 
// callback is invoked for each element in the array starting at index 0
[1, 100].reduce(getMax, 50); // 100
[    50].reduce(getMax, 10); // 50
 
// callback is invoked once for element at index 1
[1, 100].reduce(getMax);     // 100
 
// callback is not invoked
[    50].reduce(getMax);     // 50
[      ].reduce(getMax, 1);  // 1
 
[      ].reduce(getMax);     // TypeError

无初始值时 reduce() 如何运行

假如运行以下无初始值的 reduce() 代码:

1
2
3
4
5
6
7
8
9
const array = [15, 16, 17, 18, 19];
 
function reducer(previous, current, index, array) {
  const returns = previous + current;
  console.log(`previous: ${previous}, current: ${current}, index: ${index}, returns: ${returns}`);
  return returns;
}
 
array.reduce(reducer);

callback 被调用四次,每次调用的参数和返回值如下表:

callback iterationpreviousValuecurrentValuecurrentIndexarrayreturn value
first call 15 16 1 [15, 16, 17, 18, 19] 31
second call 31 17 2 [15, 16, 17, 18, 19] 48
third call 48 18 3 [15, 16, 17, 18, 19] 66
fourth call 66 19 4 [15, 16, 17, 18, 19] 85

由 reduce() 返回的值将是最后一次回调返回值(85)。

有初始值时 reduce() 如何运行

在这里,我们以相同的算法 reduce 同一个数组,但提供 10 作为初始值:

1
[15, 16, 17, 18, 19].reduce( (previousValue, currentValue, currentIndex, array) => previousValue + currentValue, 10 )
callback iterationpreviousValuecurrentValuecurrentIndexarrayreturn value
first call 10 15 0 [15, 16, 17, 18, 19] 25
second call 25 16 1 [15, 16, 17, 18, 19] 41
third call 41 17 2 [15, 16, 17, 18, 19] 58
fourth call 58 18 3 [15, 16, 17, 18, 19] 76
fifth call 76 19 4 [15, 16, 17, 18, 19] 95

这种情况下 reduce() 返回的值是 95

示例

 

求数组所有值的和

1
2
3
4
let sum = [0, 1, 2, 3].reduce(function (previousValue, currentValue) {
  return previousValue + currentValue
}, 0)
// sum is 6

你也可以写成箭头函数的形式:

1
2
3
4
let total = [ 0, 1, 2, 3 ].reduce(
  ( previousValue, currentValue ) => previousValue + currentValue,
  0
)

累加对象数组里的值

要累加对象数组中包含的值,必须提供 initialValue,以便各个 item 正确通过你的函数。

1
2
3
4
5
6
let initialValue = 0
let sum = [{x: 1}, {x: 2}, {x: 3}].reduce(function (previousValue, currentValue) {
    return previousValue + currentValue.x
}, initialValue)
 
console.log(sum) // logs 6

你也可以写成箭头函数的形式:

1
2
3
4
5
6
7
let initialValue = 0
let sum = [{x: 1}, {x: 2}, {x: 3}].reduce(
    (previousValue, currentValue) => previousValue + currentValue.x
    , initialValue
)
 
console.log(sum) // logs 6

将二维数组转化为一维

1
2
3
4
5
6
7
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  function(previousValue, currentValue) {
    return previousValue.concat(currentValue)
  },
  []
)
// flattened is [0, 1, 2, 3, 4, 5]

你也可以写成箭头函数的形式:

1
2
3
4
let flattened = [[0, 1], [2, 3], [4, 5]].reduce(
  ( previousValue, currentValue ) => previousValue.concat(currentValue),
  []
)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
let names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
 
let countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++
  }
  else {
    allNames[name] = 1
  }
  return allNames
}, {})
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

按属性对 object 分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let people = [
  { name: 'Alice', age: 21 },
  { name: 'Max', age: 20 },
  { name: 'Jane', age: 20 }
];
 
function groupBy(objectArray, property) {
  return objectArray.reduce(function (acc, obj) {
    let key = obj[property]
    if (!acc[key]) {
      acc[key] = []
    }
    acc[key].push(obj)
    return acc
  }, {})
}
 
let groupedPeople = groupBy(people, 'age')
// groupedPeople is:
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

使用扩展运算符和 initialValue 绑定包含在对象数组中的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// friends - an array of objects
// where object field "books" is a list of favorite books
let friends = [{
  name: 'Anna',
  books: ['Bible', 'Harry Potter'],
  age: 21
}, {
  name: 'Bob',
  books: ['War and peace', 'Romeo and Juliet'],
  age: 26
}, {
  name: 'Alice',
  books: ['The Lord of the Rings', 'The Shining'],
  age: 18
}]
 
// allbooks - list which will contain all friends' books +
// additional list contained in initialValue
let allbooks = friends.reduce(function(previousValue, currentValue) {
  return [...previousValue, ...currentValue.books]
}, ['Alphabet'])
 
// allbooks = [
//   'Alphabet', 'Bible', 'Harry Potter', 'War and peace',
//   'Romeo and Juliet', 'The Lord of the Rings',
//   'The Shining'
// ]

数组去重

备注: 如果你正在使用一个可以兼容Set 和 Array.from() 的环境,你可以使用let arrayWithNoDuplicates = Array.from(new Set(myArray)) 来获得一个相同元素被移除的数组。

1
2
3
4
5
6
7
8
9
let myArray = ['a', 'b', 'a', 'b', 'c', 'e', 'e', 'c', 'd', 'd', 'd', 'd']
let myArrayWithNoDuplicates = myArray.reduce(function (previousValue, currentValue) {
  if (previousValue.indexOf(currentValue) === -1) {
    previousValue.push(currentValue)
  }
  return previousValue
}, [])
 
console.log(myArrayWithNoDuplicates)

使用 .reduce() 替换 .filter().map()

使用 Array.filter() 和 Array.map() 会遍历数组两次,而使用具有相同效果的 Array.reduce() 只需要遍历一次,这样做更加高效。(如果你喜欢 for 循环,你可用使用 Array.forEach() 以在一次遍历中实现过滤和映射数组)

1
2
3
4
5
6
7
8
9
10
11
const numbers = [-5, 6, 2, 0];
 
const doubledPositiveNumbers = numbers.reduce((previousValue, currentValue) => {
  if (currentValue > 0) {
    const doubled = currentValue * 2;
    previousValue.push(doubled);
  }
  return previousValue;
}, []);
 
console.log(doubledPositiveNumbers); // [12, 4]

按顺序运行 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
 * Runs promises from array of functions that can return promises
 * in chained manner
 *
 * @param {array} arr - promise arr
 * @return {Object} promise object
 */
function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input)
  )
}
 
// promise function 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5)
  })
}
 
// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2)
  })
}
 
// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
 return a * 3
}
 
// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4)
  })
}
 
const promiseArr = [p1, p2, f3, p4]
runPromiseInSequence(promiseArr, 10)
  .then(console.log)   // 1200

使用函数组合实现管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Building-blocks to use for composition
const double = x => x + x
const triple = x => 3 * x
const quadruple = x => 4 * x
 
// Function composition enabling pipe functionality
const pipe = (...functions) => initialValue => functions.reduce(
    (acc, fn) => fn(acc),
    initialValue
)
 
// Composed functions for multiplication of specific values
const multiply6 = pipe(double, triple)
const multiply9 = pipe(triple, triple)
const multiply16 = pipe(quadruple, quadruple)
const multiply24 = pipe(double, triple, quadruple)
 
// Usage
multiply6(6)   // 36
multiply9(9)   // 81
multiply16(16) // 256
multiply24(10) // 240

使用 reduce 实现 map

1
2
3
4
5
6
7
8
9
10
11
12
if (!Array.prototype.mapUsingReduce) {
  Array.prototype.mapUsingReduce = function(callback, initialValue) {
    return this.reduce(function(mappedArray, currentValue, currentIndex, array) {
      mappedArray[currentIndex] = callback.call(initialValue, currentValue, currentIndex, array)
      return mappedArray
    }, [])
  }
}
 
[1, 2, , 3].mapUsingReduce(
  (currentValue, currentIndex, array) => currentValue + currentIndex + array.length
) // [5, 7, , 10]

规范

浏览器兼容性

Report problems with this compatibility data on GitHub
  desktopmobileserver
 
Chrome
 
Edge
 
Firefox
 
Internet Explorer
 
Opera
 
Safari
 
Chrome Android
 
Firefox for Android
 
Opera Android
 
Safari on iOS
 
Samsung Internet
 
WebView Android
 
Deno
 
Node.js
 
reduce

Legend

Full supportFull support

参见

posted on   漫思  阅读(87)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
历史上的今天:
2016-08-03 毕业5年小结一下

导航

< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8
点击右上角即可分享
微信分享提示