12/08/2018, 13:24

Tìm hiểu và refactor với reduce

Để xử lý hàng loạt phần tử trong mảng với JavaScript, ta thưởng sử dụng các hàm như map, hay filter, có một hàm nữa rất hay nhưng cá nhân mình để ý thấy ít người dùng là reduce. Vấn đề được đặt ra khi mình tìm cách refactor đoạn code sau, nhằm mục đích tính tổng các phần tử của mảng array = [1, 2, ...

Để xử lý hàng loạt phần tử trong mảng với JavaScript, ta thưởng sử dụng các hàm như map, hay filter, có một hàm nữa rất hay nhưng cá nhân mình để ý thấy ít người dùng là reduce.
Vấn đề được đặt ra khi mình tìm cách refactor đoạn code sau, nhằm mục đích tính tổng các phần tử của mảng array = [1, 2, 3]

var array = [1, 2, 3];
var sum = 0;
for (var i = 0; i < array.length; i++) {
  sum += array[i];
}
console.log(sum);

Với reduce, ta có thể refactor thành :

var array = [1, 2, 3];
var sum = array.reduce(function(prev, curr) {
  return prev + curr;
});
console.log(sum);

Thoáng nhìn code không ngắn gọn được bao nhiêu mà còn khó hiểu hơn, nhưng về logic thì mình thấy nó thật sự sáng sủa hơn rất nhiều.

Cú pháp

Theo như trong tài liệu từ Mozilla Developer Network, Array.prototype.reduce(), ta có cú pháp như sau :
arr.reduce(callback[, initialValue])
Trong function callback ta có danh sách các tham số :

  • previousValue (Bắt buộc) : Giá trị được trả về từ lần callback gần nhất hoặc là giá trị khởi tạo initialValue (Nếu là callback đầu tiên).
  • currentValue (Bắt buộc) : Giá trị của phần tử hiện tại đang được duyệt.
  • currentIndex (Tùy chọn) : Vị trí của phần tử đang được duyệt trong mảng ta đang xử lý.
  • array (Tùy chọn) : Giá trị cả mảng ta đang xử lý.

Thông tin liên quan

Array.prototype.reduce được thêm vào ECMA-265 trong phiên bản thứ 5. Nếu đang sử dụng phiên bản không hỗ trợ, bạn vẫn có thể sử dụng bằng cách khởi tạo hàm reduce theo đoạn code do MDN cung cấp :

// Production steps of ECMA-262, Edition 5, 15.4.4.21
// Reference: http://es5.github.io/#x15.4.4.21
if (!Array.prototype.reduce) {
  Array.prototype.reduce = function(callback /*, initialValue*/) {
    'use strict';
    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');
    }
    var t = Object(this), len = t.length >>> 0, k = 0, value;
    if (arguments.length == 2) {
      value = arguments[1];
    } else {
      while (k < len && !(k in t)) {
        k++;
      }
      if (k >= len) {
        throw new TypeError('Reduce of empty array with no initial value');
      }
      value = t[k++];
    }
    for (; k < len; k++) {
      if (k in t) {
        value = callback(value, t[k], k, t);
      }
    }
    return value;
  };
}

Để hiểu rõ cách sử dụng, ta sẽ xem qua các ví dụ sau.

[1, 2, 3].reduce(function(prev, curr, index) {
  console.log(prev, curr, index);
  return prev + curr;
});

Kết quả :
1 2 1
3 3 2
6

Ta có thể nhận thấy rằng callback không chứa giá trị cả phần từ đầu tiên (index = 0) mà bắt đầu với index = 1. Đó là do ta không truyền tham số initialValue, nên prev sẽ được gán giá trị đầu tiên của mảng. Tới dòng thứ 3, ta chỉ nhìn thấy một con số 6 là do ta chỉ có 2 lần callback.

[1, 2, 3].reduce(function(prev, curr, index, array) {
  console.log(prev, curr, index, array);
  return prev + curr;
}, 10);

Kết quả :
10 1 0 [1, 2, 3]
11 2 1 [1, 2, 3]
13 3 2 [1, 2, 3]
16

Ví dụ này ta đã truyền thêm tham số khởi tạo initialValue, nên ta sẽ nhìn thấy kết quả có index bắt đầu là 0 và có tất cả 3 callback ứng với 3 phần tử trong mảng.

Nếu bạn lập trình Ruby thì hàm này khá giống với inject. Hiểu rõ bản chất và sử dụng hiểu quả sẽ giúp chúng ta viết code mạch lạc, sáng sủa hơn.

0