Chi tiết về reduce trong javascript

BBMCode đã đăng vào lúc 10:06:09 20/06/2023 | đọc khoảng 12 phút, có 2357 từ

Có nhiều hàm thao tác với array trong javascript, nhưng mình sẽ dành riêng một bài dành cho reduce(), vì khá phức tạp, nhiều cách sử dụng và cũng dễ nhầm lẫn.

 

Trong thực tế, reduce() được dùng khá nhiều. Nên nắm vững sẽ giúp ích cho công việc chúng ta hơn. Bây giờ, mình cùng đi vào chi tiết từng phần nhỏ, xem reduce() có gì hay nha.

 

reduce() là gì?

reduce() là một Higher Order Function. Có thể hiểu nôm na là reduce() giúp giảm một mảng thành một giá trị. Giá trị output này có thể là number, string, array hoặc là object.

 

  • Higher order function là hàm hoạt động trên các hàm khác, bằng cách lấy chúng làm tham số hoặc trả về chúng. 
  • Nói một cách đơn giản, một Higher-Order function là hàm nhận một hàm dưới dạng đối số hoặc trả về hàm dưới dạng đầu ra.

 

Cú pháp sử dụng reduce

const reducedValue = array.reduce(function(accumulator, currentValue, currentIndex, arr), initialValue)

hoặc 

const reducedValue = array.reduce(function(accumulator, currentValue, currentIndex, arr))

 

Trong đó, function(accumulator, currentValue, currentIndex, arr) còn được gọi là reducer hoặc callbackFn

 

Tới đây thì hơi nhàm chán vì lý thuyết suông quá nhỉ, thôi chúng ta vào ví dụ cụ thể cho dễ hiểu.

 


 

1. Ví dụ đầu tiên, một ví dụ kinh điển về reduce(), tạm lấy trên developer.mozilla.org

const array1 = [1, 2, 3, 4];

// 0 + 1 + 2 + 3 + 4
const initialValue = 0;
const sumWithInitial = array1.reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  initialValue
);

console.log(sumWithInitial);
// Expected output: 10

 

Mình nghĩ, chỉ cần phân tích rõ ví dụ này, sẽ hiểu được cú pháp cũng như cách hoạt động của reduce(). Mình xin miêu tả từng lần chạy như table bên dưới.

 

callback accumulator currentValue currentIndex Giá trị trả về (reducedValue)
Lần gọi thứ 1 0 1 0 1
Lần gọi thứ 2 1 2 1 3
Lần gọi thứ 3 3 3 2 6
Lần gọi thứ 4 6 4 3 10

 

Như vậy, theo bảng ở trên, chắc anh em cũng hiểu reduce() chạy thế nào rồi ha.

 

Giá trị trả về cuối cùng là 10. Ở đây, mỗi reducer function sẽ tính tổng, cộng dồn từng phần tử trong array lại. Accumulator lúc này như một biến total. Rất dễ hiểu đúng không.

 

Tiếp theo, chúng ta sẽ đi vào từng ngữ cảnh, ví dụ cụ thể mà một developer hay dùng reduce() trong thực tế để thấy được sự đa dạng và phức tạp thế nào.

 

2. Dùng reduce() thay thế cho map()

const bbmcode = [
    {name: 'Tuan', 'age': 18},
    {name: 'Minh', 'age': 20},
    {name: 'Nga', 'age': 19},
];
// Lấy ra 1 mảng chứa các name
// Khi dùng map
const result1 = bbmcode.map(item => item.name);
console.log(result1); // ['Tuan', 'Minh', 'Nga']

// Khi dùng reduce
const result2 = bbmcode.reduce((c, item) => [...c, item.name], []);
console.log(result2); // ['Tuan', 'Minh', 'Nga']

Đơn giản đúng không, reducer lúc này là một function, với mỗi object trong mảng sẽ lấy ra name.

 

Thực tế khi nào cần dùng cái này? Khi mà anh em quá thích reduce(), hoặc đi phỏng vấn người ta hỏi thôi. Chứ đã quen dùng map() thì cũng không cần biểu diễn thêm, cứ map() nhé.

 

(về performance thì xem ở cuối bài viết)

 

💟 Tương tự với cách đó, chúng ta có thể dùng reduce()thay cho filter(), every(), some()

 

3. Bài toán thực tế: Tính tổng tiền hàng trong list orders như bên dưới

let BBMCodeOrders = [
    {id: 1, price: 1000},
    {id: 2, price: 2000},
    {id: 3, price: 3000},
    {id: 4, price: 10000},
];
// Tính tổng: 1000 + 2000 + 3000 + 10000
let sum = BBMCodeOrders.reduce(function (total, item) {
return total + item.price;
}, 0); // initial value = 0

console.log(sum); // 16000

Nhanh gọn và dễ hiểu đúng không. Với cách viết này, reducer có thể tách riêng ra làm một function, tuỳ biến và sử dụng lại trong dự án thực tế.

 

4. GroupBy bằng reduce()

Yêu cầu này rất hay gặp khi thao tác với database (API response data), dữ liệu sẽ dạng records, cần gom nhóm để thể hiện lên views thì phải làm thế nào? Với reduce() sẽ rất đơn giản và gọn lẹ. Chúng ta vào ví dụ cụ thể nha.

 

// Dữ liệu từ database (API response)
let dataInterView = [
    {id: 1, name: 'Nguyễn Văn A', city: 'Ho Chi Minh'},
    {id: 2, name: 'Nguyễn Văn B', city: 'Hai Phong'},
    {id: 3, name: 'Nguyễn Nguyên', city: 'Ho Chi Minh'},
]

// Yêu cầu là lấy danh sách các thành phố có ứng viên góp mặt và gom nhóm các ứng viên theo thành phố.
const groupBy = (array, prop) => {
    return array.reduce(function(groups, item) {
        const val = item[prop];
        groups[val] = groups[val] || [];
        groups[val].push(item);
        return groups;
    }, {})
}

const result = groupBy(dataInterView, 'city');
console.log(result); 
//{
//    Ho Chi Minh : [{id: 1, name: 'Nguyễn Văn A', city: 'Ho Chi Minh'},  {id: 3, name: 'Nguyễn Nguyên', city: 'Ho Chi Minh'}],
//    Hai Phong : [{id: 2, name: 'Nguyễn Văn B', city: 'Hai Phong'}]
//}

Code chỗ này chúng ta sẽ xây dựng reducer function có hỗ trợ tuỳ biến theo property (city, name..)

Sẽ tổng quát và tăng tính tái sử dụng.

 

5. Thêm một ví dụ kinh điển nữa, đó là làm phẳng array với reduce()

Làm phẳng array thì tất nhiên các bạn cũng có thể dùng method  flat() nha. Tuy nhiên, mình sẽ dùng reduce() để làm thử và có thể áp dụng ở các array phức tạp hơn nếu đã hiểu rõ.

 

Làm phẳng mảng thật ra là chúng ta sẽ tạo array mới, chỉ chứa giá trị của array gốc.

function flatDemo(arr = []) {
    return arr.reduce((t, v) => t.concat(Array.isArray(v) ? flatDemo(v) : v), [])
}
const bbmcode = [1,2,[5,4],5,6,7,[4,3],5,6,7,[9,10],11,12,[32,2]];
console.log(flatDemo(bbmcode)); // [1, 2, 5, 4, 5, 6, 7, 4, 3, 5, 6, 7, 9, 10, 11, 12, 32, 2]

Bài toán này rất hay gặp ở các ứng dụng liên quan đến game. Về cơ bản thì flatDemo() chạy đệ qui, hễ gặp từng phần tử là mảng thì sẽ lấy giá trị con của mảng đó.

Với các thao tác từng phần tử nhỏ trong array thì reduce() rất thích hợp để áp dụng vào trong trường hợp này.

 

6. Đếm số lượng giống nhau trong một mảng

Đây cũng là yêu cầu hay gặp với một js developer. Cùng xem ví dụ cụ thể nha.

// Yêu cầu đếm xem lớp 10A1 có bao nhiêu bạn đạt điểm >= 9
const diem_thi_lop_10a1 = [8,9,8,8,8,9,10,10,6,9,10,8,9.5,10,9,8.5,8,9,10];

function bbmcodeCount(arr) {
    return arr.reduce((t, v) => v >=9 ? (t[v] = (t[v] || 0) + 1, t) : t, {});
}

bbmcodeCount(diem_thi_lop_10a1); // {9: 5, 10: 5, 9.5: 1}

 

Tất nhiên cũng có thể giải quyết bài toán bằng cách for từng phần tử. Nhưng nếu quen dùng reduce() bạn sẽ thấy tiện và code gọn hơn nhiều.

 

7. Chuyển đổi array object sang object bằng reduce()

const points = [
    { id: 1, username: "bbmcode", point: 111 },
    { id: 2, username: "bbmcode2", point: 2 },
    { id: 3, username: "bbmcode3", point: 32 },
];
const result = points.reduce((t, v) => {
    const { username, ...rest } = v;
    t[username] = rest;
    return t;
}, {});

console.log(result);
//{
//    bbmcode: { id: 1, point: 111 },
//    bbmcode2: { id: 2, point: 2 },
//    bbmcode3: { id: 3, point: 32 }
//}

 

Trong code trên, mình có dùng rest, để lấy các thành phần khác ngoài username như id, point. Nếu bạn nào chưa rõ về rest parameters, destructuring, spread thì có thể xem bài viết này nha

https://bbmcode.com/destructuring-spread-operator-va-rest-parameters-trong-javascript

 

Trải qua 7 bài toán thực tế, chắc chúng ta cũng nắm rõ về reduce() rồi nhỉ. Cái quan trọng nhất vẫn là hiểu nó, lúc đã hiểu rồi thì gặp vấn đề có thể áp dụng được.

 


♒ Phần cuối của bài viết này mình sẽ làm một so sánh nhỏ về performance code khi dùng reduce(), map(), forEach()for().

 

Không xét về thói quen và style lập trình, hãy cùng nhau check thử xem khi thao tác với mảng, thì method nào chạy nhanh nhất nhé.

 

🔢 Yêu cầu: Hãy viết hàm tính tổng một mảng các số nguyên từ 0 đến 90000

const numbers = [...new Array(90000).keys()];

// dùng for
console.time("for");
let result1 = 0;
let ln = numbers.length;
for (let i = 0; i < ln ; i++) {
    result1 += i + 1;
}
console.timeEnd("for");

// dùng forEach
console.time("forEach");
let result2 = 0;
numbers.forEach(v => (result2 += v + 1));
console.timeEnd("forEach");

// dùng map
console.time("map");
let result3 = 0;
numbers.map(v => (result3 += v + 1, v));
console.timeEnd("map");

// dùng reduce
console.time("reduce");
const result4 = numbers.reduce((t, v) => t + v + 1, 0);
console.timeEnd("reduce");

//Kết quả:
// for: 3.2509765625 ms
// forEach: 2.475830078125 ms
// map: 2.323974609375 ms
// reduce: 1.820068359375 ms

 

Lưu ý: Tùy bài toán, tùy lượng dữ liệu và xử lý bên trong mà quyết định dùng method nào nha anh em. Về cơ bản thì ví dụ trên như con gà rụng cọng lông đuôi thôi, chỉ mang tính chất tham khảo.

 


 

❤️ Kết luận: reduce() không khó nhưng cũng không dễ, nắm kỹ và vận dụng tốt sẽ giúp chúng ta nâng cao kỹ năng lập trình và giải quyết vấn đề nhanh chóng hơn. Văn ôn võ luyện, hãy bookmark bài này lại, khi nào có thời gian cứ lướt lại xem các bạn nhé.

 

Cảm ơn đã đọc đến đây, bài viết khá dài rồi!