--- title: Array.prototype.reduce() slug: Web/JavaScript/Reference/Global_Objects/Array/Reduce translation_of: Web/JavaScript/Reference/Global_Objects/Array/Reduce ---
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.
arr.reduce(callback[, initialValue])
callback
accumulator
initialValue
, nếu được cung cấp (xem bên dưới).currentValue
currentIndex
{{optional_inline}}initialValue
được cung cấp, và tại 1 nếu không có initialValue
.array
{{optional_inline}}reduce()
.initialValue
{{optional_inline}}accumulator
) của hàm callback
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ọi reduce()
trên một mảng rỗng và không có giá trị ban đầu sẽ gây ra lỗi.Giá trị sau khi rút gọn.
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 {{jsxref("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 );
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 {{jsxref("Functions/Arrow_functions", "Arrow Function","",1)}} 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
.
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ị 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
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), [] );
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 }
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 }] // }
// 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' // ]
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]
/** * 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
// 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
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]
// 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ả | Trạng thái | Ghi chú |
---|---|---|
{{SpecName('ES5.1', '#sec-15.4.4.21', 'Array.prototype.reduce()')}} | {{Spec2('ES5.1')}} | Định nghĩa lần đầu. Hiện thực trong JavaScript 1.8. |
{{SpecName('ES6', '#sec-array.prototype.reduce', 'Array.prototype.reduce()')}} | {{Spec2('ES6')}} | |
{{SpecName('ESDraft', '#sec-array.prototype.reduce', 'Array.prototype.reduce()')}} | {{Spec2('ESDraft')}} |
{{Compat("javascript.builtins.Array.reduce")}}