Phương thức reduce()
dùng để thực thi một hàm lên từng phần tử của mảng (từ trái sang phải) với một biến tích lũy để thu về một giá trị duy nhất.
The source for this interactive example is stored in a GitHub repository. If you'd like to contribute to the interactive examples project, please clone https://github.com/mdn/interactive-examples and send us a pull request.
Cú pháp
arr.reduce(callback[, initialValue])
Các tham số
callback
- Hàm dùng để thực thi với từng phần tử (element) của mảng, nhận vào 04 tham số:
accumulator
- Biến tích lũy, truyền giá trị trả về của mỗi lần gọi callback; nó là giá trị tích lũy được trả về trong lần gọi callback trước, hoặc giá trị của tham số
initialValue
, nếu được cung cấp (xem bên dưới). currentValue
- Phần tử trong mảng hiện tại đang được xử lý.
currentIndex
Optional- Chỉ mục (index) của phần tử đang được xử lý. Bắt đầu tại 0, nếu giá trị
initialValue
được cung cấp, và tại 1 nếu không cóinitialValue
. array
Optional- Mảng đang được gọi với
reduce()
.
initialValue
Optional- Giá trị cho tham số thứ nhất (
accumulator
) của hàmcallback
trong lần gọi đầu tiên. Nếu giá trị ban đầu này không được cung cấp, phần tử đầu tiên của mảng sẽ được dùng. Do đó, gọireduce()
trên một mảng rỗng và không có giá trị ban đầu sẽ gây ra lỗi.
Giá trị trả về
Giá trị sau khi rút gọn.
Mô tả
reduce()
thực thi hàm callback
lên từng phần tử đang tồn tại trong mảng, bỏ qua những lỗ trống không giá trị, và nhận vào 04 tham số:
accumulator
currentValue
currentIndex
array
Trong lần đầu tiên callback
được gọi, accumulator
and currentValue
có thể có một trong hai giá trị. Nếu tham số initialValue
được cung cấp cho reduce()
, thì accumulator
sẽ bằng initialValue
, và currentValue
sẽ bằng phần tử đầu tiên của mảng. Nếu không có initialValue
, accumulator
sẽ bằng phần tử đầu tiên của mảng, và currentValue
sẽ bằng phần tử thứ hai.
Ghi chú: Nếu initialValue
không được cung cấp, reduce()
sẽ thực thi callback bắt đầu từ index 1, bỏ qua index đầu tiên. Nếu initialValue
được cung cấp, index sẽ bắt đầu từ 0.
Nếu mảng rỗng, và initialValue
không được cung cấp, gọi reduce()
sẽ gây ra lỗi TypeError
. Nếu mảng chỉ có một phần tử có giá trị (bất kể vị trí index) đồng thời không có initialValue
, hoặc có initialValue
nhưng mảng lại rỗng, thì giá trị duy nhất đó sẽ được trả về và callback
sẽ không được gọi.
Sẽ an toàn hơn nếu giá trị ban đầu được cung cấp, bởi vì có đến ba khả năng xảy ra nếu không có initialValue
như ở ví dụ sau:
var maxCallback = ( acc, cur ) => Math.max( acc.x, cur.x ); var maxCallback2 = ( max, cur ) => Math.max( max, cur ); // reduce() không có initialValue [ { x: 22 }, { x: 42 } ].reduce( maxCallback ); // 42 [ { x: 22 } ].reduce( maxCallback ); // { x: 22 } [ ].reduce( maxCallback ); // TypeError // map/reduce; giải pháp hay hơn, và nó có thể áp dụng được cho mảng rỗng hoặc lớn hơn [ { x: 22 }, { x: 42 } ].map( el => el.x ) .reduce( maxCallback2, -Infinity );
Cách reduce() làm việc
Giả sử có một đoạn code với reduce()
được hiện thực như sau:
[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array) { return accumulator + currentValue; });
Callback sẽ được gọi bốn lần, với giá trị tham số và trả về trong mỗi lần gọi như sau:
callback |
accumulator |
currentValue |
currentIndex |
array |
giá trị trả về |
---|---|---|---|---|---|
lần gọi thứ nhất | 0 |
1 |
1 | [0, 1, 2, 3, 4] |
1 |
lần gọi thứ hai | 1 |
2 |
2 | [0, 1, 2, 3, 4] |
3 |
lần gọi thứ ba | 3 |
3 |
3 | [0, 1, 2, 3, 4] |
6 |
lần gọi thứ tư | 6 |
4 |
4 | [0, 1, 2, 3, 4] |
10 |
Giá trị trả về cho reduce()
chính là giá trị trả về của lần gọi callback cuối cùng (10
).
Bạn cũng có thể cung cấp một hàm mũi tên Arrow Function thay vì một hàm đầy đủ. Đoạn code sau đây sẽ cho kết quả giống như đoạn code ở trên:
[0, 1, 2, 3, 4].reduce( (accumulator, currentValue, currentIndex, array) => accumulator + currentValue );
Nếu bạn cung cấp giá trị initialValue
cho tham số thứ hai của hàm reduce()
, thì kết quả sẽ như bên dưới:
[0, 1, 2, 3, 4].reduce((accumulator, currentValue, currentIndex, array) => { return accumulator + currentValue; }, 10);
callback |
accumulator |
currentValue |
currentIndex |
array |
giá trị trả về |
---|---|---|---|---|---|
lần gọi thứ nhất | 10 |
0 |
0 |
[0, 1, 2, 3, 4] |
10 |
lần gọi thứ hai | 10 |
1 |
1 |
[0, 1, 2, 3, 4] |
11 |
lần gọi thứ ba | 11 |
2 |
2 |
[0, 1, 2, 3, 4] |
13 |
lần gọi thứ tư | 13 |
3 |
3 |
[0, 1, 2, 3, 4] |
16 |
lần gọi thứ năm | 16 |
4 |
4 |
[0, 1, 2, 3, 4] |
20 |
Giá trị trả về cho reduce()
lần này sẽ là 20
.
Ví dụ
Tính tổng của tất cả các phần tử của mảng
var sum = [0, 1, 2, 3].reduce(function (accumulator, currentValue) { return accumulator + currentValue; }, 0); // sum is 6
Tương tự, nhưng viết bằng hàm arrow function
var total = [ 0, 1, 2, 3 ].reduce( ( accumulator, currentValue ) => accumulator + currentValue, 0 );
Tính tổng các giá trị bên trong một mảng các object
Để tính tổng các giá trị nằm bên trong các phần tử là object, bạn phải cung cấp một giá trị ban đầu để từng phần tử đều được callback chạy qua (và accumulator
luôn luôn là giá trị kiểu số):
var initialValue = 0; var sum = [{x: 1}, {x:2}, {x:3}].reduce(function (accumulator, currentValue) { return accumulator + currentValue.x; },initialValue) console.log(sum) // logs 6
Tương tự, viết bằng arrow function:
var initialValue = 0; var sum = [{x: 1}, {x:2}, {x:3}].reduce( (accumulator, currentValue) => accumulator + currentValue.x ,initialValue ); console.log(sum) // logs 6
Trải phẳng một mảng chứa nhiều mảng con
var flattened = [[0, 1], [2, 3], [4, 5]].reduce( function(accumulator, currentValue) { return accumulator.concat(currentValue); }, [] ); // flattened is [0, 1, 2, 3, 4, 5]
Tương tự, viết bằng arrow function:
var flattened = [[0, 1], [2, 3], [4, 5]].reduce( ( accumulator, currentValue ) => accumulator.concat(currentValue), [] );
Đếm số lần xuất hiện của phần tử trong mảng
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']; var 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 }
Nhóm các đối tượng theo giá trị property nào đó
var people = [ { name: 'Alice', age: 21 }, { name: 'Max', age: 20 }, { name: 'Jane', age: 20 } ]; function groupBy(objectArray, property) { return objectArray.reduce(function (acc, obj) { var key = obj[property]; if (!acc[key]) { acc[key] = []; } acc[key].push(obj); return acc; }, {}); } var groupedPeople = groupBy(people, 'age'); // groupedPeople is: // { // 20: [ // { name: 'Max', age: 20 }, // { name: 'Jane', age: 20 } // ], // 21: [{ name: 'Alice', age: 21 }] // }
Ghép các mảng con bên trong các object sử dụng toán tử spread và initialValue
// friends - an array of objects // where object field "books" - list of favorite books var 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 var allbooks = friends.reduce(function(accumulator, currentValue) { return [...accumulator, ...currentValue.books]; }, ['Alphabet']); // allbooks = [ // 'Alphabet', 'Bible', 'Harry Potter', 'War and peace', // 'Romeo and Juliet', 'The Lord of the Rings', // 'The Shining' // ]
Xóa các phần tử bị trùng trong mảng
let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4]; let result = arr.sort().reduce((accumulator, current) => { const length = accumulator.length if (length === 0 || accumulator[length - 1] !== current) { accumulator.push(current); } return accumulator; }, []); console.log(result); //[1,2,3,4,5]
Chạy các Promise theo trình tự
/** * 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
Tổ hợp các hàm và gọi chuyền (piping)
// 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) => input => functions.reduce( (acc, fn) => fn(acc), input ); // 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
Hiện thực lại map() sử dụng reduce()
if (!Array.prototype.mapUsingReduce) { Array.prototype.mapUsingReduce = function(callback, thisArg) { return this.reduce(function(mappedArray, currentValue, index, array) { mappedArray[index] = callback.call(thisArg, currentValue, index, array); return mappedArray; }, []); }; } [1, 2, , 3].mapUsingReduce( (currentValue, index, array) => currentValue + index + array.length ); // [5, 7, , 10]
Polyfill
// Production steps of ECMA-262, Edition 5, 15.4.4.21 // Reference: http://es5.github.io/#x15.4.4.21 // https://tc39.github.io/ecma262/#sec-array.prototype.reduce if (!Array.prototype.reduce) { Object.defineProperty(Array.prototype, 'reduce', { value: function(callback /*, initialValue*/) { if (this === null) { throw new TypeError( 'Array.prototype.reduce ' + 'called on null or undefined' ); } if (typeof callback !== 'function') { throw new TypeError( callback + ' is not a function'); } // 1. Let O be ? ToObject(this value). var o = Object(this); // 2. Let len be ? ToLength(? Get(O, "length")). var len = o.length >>> 0; // Steps 3, 4, 5, 6, 7 var k = 0; var value; if (arguments.length >= 2) { value = arguments[1]; } else { while (k < len && !(k in o)) { k++; } // 3. If len is 0 and initialValue is not present, // throw a TypeError exception. if (k >= len) { throw new TypeError( 'Reduce of empty array ' + 'with no initial value' ); } value = o[k++]; } // 8. Repeat, while k < len while (k < len) { // a. Let Pk be ! ToString(k). // b. Let kPresent be ? HasProperty(O, Pk). // c. If kPresent is true, then // i. Let kValue be ? Get(O, Pk). // ii. Let accumulator be ? Call( // callbackfn, undefined, // « accumulator, kValue, k, O »). if (k in o) { value = callback(value, o[k], k, o); } // d. Increase k by 1. k++; } // 9. Return accumulator. return value; } }); }
Nếu bạn thực sự cần chức năng này trên những engine JavaScript không hỗ trợ Object.defineProperty()
, bạn không nên thêm polyfill này vào Array.prototype
bởi vì không có cách nào làm cho nó không-duyệt-qua (non-enumerable) được (property mới sẽ xuất hiện trong các vòng lặp for).
Đặc tả
Đặc tả | Trạng thái | Ghi chú |
---|---|---|
ECMAScript 5.1 (ECMA-262) The definition of 'Array.prototype.reduce()' in that specification. |
Standard | Định nghĩa lần đầu. Hiện thực trong JavaScript 1.8. |
ECMAScript 2015 (6th Edition, ECMA-262) The definition of 'Array.prototype.reduce()' in that specification. |
Standard | |
ECMAScript Latest Draft (ECMA-262) The definition of 'Array.prototype.reduce()' in that specification. |
Draft |
Tương thích trình duyệt
Desktop | Mobile | Server | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
reduce | Chrome Full support 3 | Edge Full support 12 | Firefox Full support 3 | IE Full support 9 | Opera Full support 10.5 | Safari Full support 4.1 | WebView Android Full support ≤37 | Chrome Android Full support 18 | Firefox Android Full support 4 | Opera Android Full support Yes | Safari iOS Full support 4 | Samsung Internet Android Full support 1.0 | nodejs Full support Yes |
Legend
- Full support
- Full support