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()
时提供了 initialValue,previousValue 取值则为 initialValue,currentValue 则取数组中的第一个值。- 如果没有提供 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 iteration | previousValue | currentValue | currentIndex | array | return 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 iteration | previousValue | currentValue | currentIndex | array | return 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 GitHubdesktop | mobile | server | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
reduce |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
2016-08-03 毕业5年小结一下