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 lũy kế để thu về một giá trị duy nhất.

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 lũy kế, truyền giá trị trả về của mỗi lần gọi callback; nó là giá trị lũy kế được trả về trong lần gọi callback trước, hoặc giá trị 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ý.
currentIndexOptional
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.
arrayOptional
Mảng đang được gọi với reduce().
initialValueOptional
Giá trị cho tham số thứ nhất (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ị 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ó initialValueaccumulator 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

Update compatibility data on GitHub
DesktopMobileServer
ChromeEdgeFirefoxInternet ExplorerOperaSafariAndroid webviewChrome for AndroidEdge MobileFirefox for AndroidOpera for AndroidiOS SafariSamsung InternetNode.js
Basic supportChrome Full support YesEdge Full support YesFirefox Full support 3IE Full support 9Opera Full support 10.5Safari Full support 4WebView Android Full support YesChrome Android Full support YesEdge Mobile Full support YesFirefox Android Full support 4Opera Android Full support YesSafari iOS Full support YesSamsung Internet Android Full support Yesnodejs Full support Yes

Legend

Full support  
Full support

Xem thêm

Document Tags and Contributors

Những người đóng góp cho trang này: trongthanh
Cập nhật lần cuối bởi: trongthanh,