--- title: Array.prototype.reduce() slug: Web/JavaScript/Reference/Global_Objects/Array/Reduce tags: - Array - ECMAScript 5 - JavaScript - Method - Prototype - Reduce - Reference translation_of: Web/JavaScript/Reference/Global_Objects/Array/Reduce --- {{JSRef}} **`reduce()`** 方法对数组中的每个元素按序执行一个由您提供的 **reducer** 函数,每一次运行 **reducer** 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。 第一次执行回调函数时,不存在“上一次的计算结果”。如果需要回调函数从数组索引为 0 的元素开始执行,则需要传递初始值。否则,数组索引为 0 的元素将被作为初始值 *initialValue*,迭代器将从第二个元素开始执行(索引为 1 而不是 0)。 下面的例子能够帮助你理解 `reduce()` 的用处——计算数组所有元素的总和: {{EmbedInteractiveExample("pages/js/array-reduce.html")}} **reducer** 逐个遍历数组元素,每一步都将当前元素的值与上一步的计算结果相加(上一步的计算结果是当前元素之前所有元素的总和)——直到没有更多的元素被相加。 ## 语法 ```js // 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` {{optional_inline}} - : 作为第一次调用 `callback` 函数时参数 *previousValue* 的值。若指定了初始值 `initialValue`,则 `currentValue` 则将使用数组第一个元素;否则 `previousValue` 将使用数组第一个元素,而 `currentValue` 将使用数组第二个元素。 ### 返回值 使用 “reducer” 回调函数遍历整个数组后的结果。 ### 异常 - {{jsxref("TypeError")}} - : 数组为空且初始值 `initialValue` 未提供。 ## 描述 ECMAScript 规范描述了 `reduce()` 的行为: > *callbackfn* 应是一个接受四个参数的函数,`reduce` 对于数组中第一个元素之后的每一个元素,按升序各调用一次回调函数。 > > *callbackfn* 被调用时会传入四个参数: > > - *previousValue*(前一次调用 *callbackfn* 得到的返回值) > - *currentValue*(数组中正在处理的元素) > - *currentIndex*(数组中正在处理的元素的索引) > - 被遍历的对象 > > 回调函数第一次执行时,*previousValue* 和 *currentValue* 的取值有两种情况: > - 如果调用 `reduce()` 时提供了 *initialValue*,*previousValue* 取值则为 *initialValue*,*currentValue* 则取数组中的第一个值。 > - 如果没有提供 *initialValue*,那么 *previousValue* 取数组中的第一个值,*currentValue* 取数组中的第二个值。 > > 如果数组为空且为指定初始值 *initialValue*,则会抛出 {{jsxref("TypeError")}}。 > > `reduce` 不会直接改变调用它的对象,但对象可被调用的 *callbackfn* 所改变。 > > 遍历的元素范围是在第一次调用 *callbackfn* 之前确定的。所以即使有元素在调用开始后被追加到数组中,这些元素也不会被 *callbackfn* 访问。如果数组现有的元素发生了变化,传递给 *callbackfn* 的值将会是元素被 `reduce` 访问时的值(即发生变化后的值);在调用 `reduce` 开始后,尚未被访问的元素若被删除,则其将不会被 `reduce` 访问。 如果数组仅有一个元素(无论位置如何)并且没有提供初始值 *initialValue*,或者有提供 *initialValue* 但是数组为空,那么此唯一值将被返回且 `callbackfn` 不会被执行。 提供初始值 *initialValue* 通常更安全,正如下面的例子,如果没有提供 *initialValue*,则 `reduce` 方法会因数组长度的不同(大于 1、等于 1、等于 0)而有不同的表现: ```js 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()` 代码: ```js 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` 作为初始值: ```js [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`。 ## 示例 ### 求数组所有值的和 ```js let sum = [0, 1, 2, 3].reduce(function (previousValue, currentValue) { return previousValue + currentValue }, 0) // sum is 6 ``` 你也可以写成箭头函数的形式: ```js let total = [ 0, 1, 2, 3 ].reduce( ( previousValue, currentValue ) => previousValue + currentValue, 0 ) ``` ### 累加对象数组里的值 要累加对象数组中包含的值,**必须**提供 *initialValue*,以便各个 item 正确通过你的函数。 ```js 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 ``` 你也可以写成箭头函数的形式: ```js let initialValue = 0 let sum = [{x: 1}, {x: 2}, {x: 3}].reduce( (previousValue, currentValue) => previousValue + currentValue.x , initialValue ) console.log(sum) // logs 6 ``` ### 将二维数组转化为一维 ```js let flattened = [[0, 1], [2, 3], [4, 5]].reduce( function(previousValue, currentValue) { return previousValue.concat(currentValue) }, [] ) // flattened is [0, 1, 2, 3, 4, 5] ``` 你也可以写成箭头函数的形式: ```js let flattened = [[0, 1], [2, 3], [4, 5]].reduce( ( previousValue, currentValue ) => previousValue.concat(currentValue), [] ) ``` ### 计算数组中每个元素出现的次数 ```js 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 分类 ```js 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 绑定包含在对象数组中的数组 ```js // 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' // ] ``` ### 数组去重 > **备注:** 如果你正在使用一个可以兼容{{jsxref("Set")}} 和 {{jsxref("Array.from()")}} 的环境,你可以使用`let arrayWithNoDuplicates = Array.from(new Set(myArray))` 来获得一个相同元素被移除的数组。 ```js 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() 使用 {{jsxref("Array.filter()")}} 和 {{jsxref("Array.map()")}} 会遍历数组两次,而使用具有相同效果的 {{jsxref("Array.reduce()")}} 只需要遍历一次,这样做更加高效。(如果你喜欢 `for` 循环,你可用使用 {{jsxref("Array.forEach()")}} 以在一次遍历中实现过滤和映射数组) ```js 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 ```js /** * 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 ``` ### 使用函数组合实现管道 ```js // 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 ```js 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] ``` ## 规范 {{Specifications}} ## 浏览器兼容性 {{Compat}} ## 参见 - [Polyfill of `Array.prototype.reduce` in `core-js`](https://github.com/zloirock/core-js#ecmascript-array) - {{jsxref("Array.prototype.reduceRight()")}}