Gom một danh sách theo trạng thái thường chỉ cần khóa string. Nhưng nếu khóa có thể là số, boolean hoặc một object đại diện nhóm, kết quả không còn tương đương. Chọn Object.groupBy hay Map.groupBy nên bắt đầu từ hợp đồng khóa và cách phần còn lại của app đọc dữ liệu.
Bài đối chiếu API ngày 29/10/2026 và chạy các ví dụ trên Node.js 24.18.1. Đây là so sánh ngữ nghĩa và cách tích hợp, không có benchmark tốc độ hay bộ nhớ. Việc chạy được trên Node không chứng minh mọi browser hoặc JavaScript engine trong mobile đều hỗ trợ sẵn hai API.
Cùng dữ liệu, hai kiểu kết quả
const tasks = [
{ id: 1, status: 'todo' },
{ id: 2, status: 'done' },
{ id: 3, status: 'todo' },
];
const byObject = Object.groupBy(tasks, task => task.status);
const byMap = Map.groupBy(tasks, task => task.status);
console.log(byObject.todo.map(task => task.id));
console.log(byMap.get('todo').map(task => task.id));
console.log(Object.getPrototypeOf(byObject) === null);
Hai dòng đầu đều cho danh sách ID [1, 3]; dòng cuối là true. Object đầu ra không kế thừa Object.prototype, vì vậy đừng gọi byObject.hasOwnProperty(...). Có thể dùng Object.hasOwn(byObject, 'todo') nếu cần phân biệt có nhóm với không có nhóm.
Object.groupBy chuyển khóa không phải string hoặc symbol thành string. Map.groupBy giữ khóa dưới dạng giá trị của Map. Khác biệt này mới là điểm cần thử trước khi thay thế API.
Số 1 và chuỗi 1 có cùng một nhóm không?
const records = [{ key: 1 }, { key: '1' }];
const objectGroups = Object.groupBy(records, item => item.key);
const mapGroups = Map.groupBy(records, item => item.key);
console.log(Object.keys(objectGroups));
console.log(objectGroups['1'].length);
console.log(mapGroups.size, mapGroups.get(1).length, mapGroups.get('1').length);
Kết quả là ['1'], 2, rồi 2 1 1. Nếu schema cho phép hai kiểu khóa mang nghĩa khác nhau, Object đã làm chúng nhập thành một nhóm. Nếu nghiệp vụ thực sự coi chúng là cùng ID, nên chuẩn hóa có chủ đích ở biên dữ liệu thay vì tình cờ dựa vào ép kiểu.
Map không tự hiểu hai object có cùng nội dung là cùng khóa. Đó là một hợp đồng nhận diện khác, cần kiểm tra khi dữ liệu được tải lại từ JSON.
const teamA = { id: 'A' };
const teamB = { id: 'A' };
const work = [{ team: teamA }, { team: teamB }];
const groups = Map.groupBy(work, item => item.team);
console.log(groups.size);
console.log(groups.get(teamA).length);
console.log(groups.get({ id: 'A' }));
Đoạn này in 2, 1, undefined. Nếu muốn gom theo mã nhóm ổn định qua các lượt fetch, callback nên trả item.team.id. Dùng object làm khóa chỉ phù hợp khi bạn cần giữ nhận diện đúng instance ấy. Map reference giải thích quy tắc khóa và việc duyệt theo thứ tự chèn.
Cả hai đều giữ tham chiếu đến phần tử
byObject.todo[0].status = 'blocked';
console.log(tasks[0].status);
console.log(byMap.get('todo')[0].status);
Cả hai dòng đều in blocked. GroupBy tạo cấu trúc nhóm, không deep clone task. Hơn nữa, task vừa đổi status vẫn đang nằm trong nhóm tên todo: kết quả nhóm là một lần tính tại thời điểm gọi, không tự chuyển phần tử sang nhóm mới.
Với UI cập nhật dữ liệu, hãy tính lại nhóm từ danh sách mới hoặc quản lý việc di chuyển nhóm rõ ràng. Không dùng kết quả này làm bản sao cách ly để sửa thử dữ liệu. Đây là ca kiểm tra hữu ích cho reducer hoặc selector của ứng dụng.
So sánh theo cùng tiêu chí
| Tiêu chí | Object.groupBy | Map.groupBy |
|---|---|---|
| Bắt đầu với trạng thái string | Truy cập thuộc tính quen thuộc | Truy cập bằng get |
| Quyền kiểm soát khóa | String/symbol, có ép kiểu | Giữ kiểu và identity của khóa |
| Tích hợp và hệ sinh thái | Dễ dùng với Object.entries và dữ liệu string-key | Hợp với code đã dùng Map |
| Hiệu năng | Chưa đo trong bài | Chưa đo trong bài |
| Chi phí duy trì | Cần nhớ null prototype và ép khóa | Cần giữ đúng khóa, chuyển dạng khi lưu JSON |
| Trường hợp phù hợp | Nhóm theo mã hoặc trạng thái string | Nhóm cần khóa khác string hoặc object identity |
Nếu API cần JSON, hãy thiết kế dạng truyền đi trước. JSON.stringify không tự biến nội dung Map thành một danh sách nhóm; chuyển sang mảng entry hoặc schema riêng. Với Object, symbol cũng không phải khóa JSON thông thường. Đừng coi tiện ghi JSON là lý do bỏ qua mất kiểu khóa.
Kiểm tra trước khi áp dụng
Thử ít nhất dữ liệu rỗng, khóa số lẫn string, hai object giống nội dung nhưng khác instance và phần tử bị đổi sau khi nhóm. Kiểm tra hỗ trợ API trên runtime đích, rồi mới quyết định có cần polyfill hay cách gom thủ công.
Với danh sách task chỉ dùng status string, Object thường cho hợp đồng đơn giản. Khi yêu cầu thật sự cần giữ kiểu khóa, Map giúp thể hiện điều đó trực tiếp. Quyết định cuối cùng nên dựa vào cách đọc, lưu và cập nhật nhóm trong app, không dựa vào giả định một API luôn nhanh hơn.


