Групування масиву за ознакою довго писали вручну через reduce. З ES2024 для цього є вбудовані функції.
Object.groupBy(items, callback) - результат у звичайному об'єкті, ключі - те, що повернув колбек:
const orders = [
{ id: 1, status: 'paid', total: 100 },
{ id: 2, status: 'new', total: 50 },
{ id: 3, status: 'paid', total: 70 },
];
const byStatus = Object.groupBy(orders, (order) => order.status);
// { paid: [{ id: 1, ... }, { id: 3, ... }], new: [{ id: 2, ... }] }
const bySize = Object.groupBy(orders, ({ total }) => (total >= 70 ? 'large' : 'small'));
Map.groupBy(items, callback) - результат у Map. Потрібен, коли ключ - не рядок: об'єкт, дата, число, яке не має перетворюватися на рядок:
const byCustomer = Map.groupBy(orders, (order) => customersById.get(order.customerId));
byCustomer.get(someCustomer); // замовлення конкретного об'єкта-клієнта
Особливості:
- це статичні функції, а не методи масиву:
Object.groupBy(arr, fn), а неarr.groupBy(fn). Метод масиву планували, але він конфліктував зі старими бібліотеками, що розширювалиArray.prototype; - об'єкт від
Object.groupByмає прототипnull: у нього немаєhasOwnProperty,toStringтощо.result.hasOwnProperty('paid')кине помилку - перевіряйте черезObject.hasOwn(result, 'paid')чи'paid' in result; - групи відсутніх значень просто не створюються - порожніх масивів для «ненайдених» статусів не буде;
- елементи в групах ідуть у порядку вихідного масиву.
Як це робили раніше (і досі доводиться в старих оточеннях):
const byStatus = orders.reduce((groups, order) => {
(groups[order.status] ??= []).push(order);
return groups;
}, {});
Порівняння з Laravel: це аналог collect($orders)->groupBy('status'). Агрегати по групах далі рахують звичайними методами: Object.entries(byStatus).map(([status, list]) => [status, list.length]).
Підтримка: усі сучасні браузери з 2024 року і Node.js 21+. Для старших середовищ - поліфіл (core-js) чи reduce.