Az Object.groupBy() kényelmesnek tűnik: beadunk egy tömböt és egy kulcsot előállító függvényt, visszakapjuk a csoportokat. A meglepetés akkor jön, amikor az eredményen megszokásból meghívjuk a hasOwnProperty-t. Nem létezik rajta. Ez nem hiányos implementáció, hanem szándékos API-döntés.
A Object.groupBy() null-prototípusú objektumot ad vissza. Vagyis az eredmény prototípusa null, nem Object.prototype. Nincs örökölt toString, constructor vagy hasOwnProperty; a csoportnevek viszont saját property-k. A specifikáció ezt közvetlenül null-prototípusú objektum létrehozásával írja elő.
A __proto__ is csak egy csoportnév
Ez a választás nem pusztán kényelmetlenség. Egy normál objektumnál a __proto__ név történelmileg különleges: a prototípushoz kötődik. A csoportosító API eredményében viszont biztonságosan lehet ilyen nevű saját kulcs. Külső adatból képzett címkéknél ez kifejezetten hasznos tulajdonság.
const rows = [
{ customer: 'acme', total: 120 },
{ customer: 'beta', total: 80 },
{ customer: '__proto__', total: 40 },
];
const byCustomer = Object.groupBy(rows, ({ customer }) => customer);
console.log(Object.getPrototypeOf(byCustomer) === null); // true
console.log(byCustomer.__proto__.map(({ total }) => total)); // [40]
console.log(Object.hasOwn(byCustomer, 'acme')); // true
console.log(byCustomer.hasOwnProperty); // undefined
A saját kulcs ellenőrzésére ezért az Object.hasOwn() a jó forma. Nem függ attól, van-e prototípus, és nincs szükség a régi, körülményes Object.prototype.hasOwnProperty.call(...) mintára sem. A példát Node 22.22.3-mal futtattam; a kimenet sorban true, [ 40 ], true, undefined.
A visszatérő értékből property-kulcs lesz
Az Object.groupBy() callbackjének eredménye property-kulccsá alakul. Egy 1 szám kulcsa tehát a "1" property, nem egy numerikus azonosító; a string és a szám ugyanabba a rekeszbe kerülhet. Ez teljesen természetes, ha a csoportok nevei például státuszok vagy dátumok, és jól együttműködik az objektumokkal, valamint a JSON-nal.
A csoportokban az eredeti elemek referenciái vannak, nem mély másolatok. Ha a byCustomer.acme[0].total mezőt módosítjuk, az eredeti rows[0] is módosul. A groupBy osztályoz, nem izolált adatszerkezetet épít.
Miért jobb ez a kézzel írt reduce()-nál?
A korábbi, elterjedt minta egy reduce() volt: (acc[key] ??= []).push(item). Ez rövid, de egy üres objektummal induló accumulatornál a __proto__ és más örökölt nevek külön kezelést kérnek. A körültekintő változat már Object.create(null)-t használ, amellyel ugyanezt a szótárviselkedést építjük fel kézzel. Az Object.create() pontosan ezt a prototípus nélküli alakot teszi lehetővé.
Az Object.groupBy() ezt a biztonságos alapot és a tömbök létrehozását adja készre. A reduce() továbbra is jobb választás, ha csoportosítás közben rögtön összeget, darabszámot vagy más aggregátumot akarunk számolni, mert ott nem kell köztes elem-listákat felépíteni. A két API nem teljesítményverseny: eltérő eredményformátumot céloznak.
Mikor kell inkább Map.groupBy()?
Ha a csoport kulcsa nem név, hanem maga egy objektum, a Map.groupBy() a pontosabb eszköz. A visszaadott Map nem alakítja property-névvé a callback értékét: objektumot, függvényt vagy más tetszőleges értéket kulcsként használhat. Így egy konfiguráció- vagy állapotobjektum azonossága valóban megmarad.
const open = { state: 'open' };
const closed = { state: 'closed' };
const byState = Map.groupBy(rows, row =>
row.total >= 100 ? open : closed
);
console.log(byState.get(open)); // az acme sora
console.log([...byState.keys()].map(key => key.state));
// ['open', 'closed']
A Map.groupBy specifikációja ugyanúgy tömböket épít csoportonként, de a kulcsok a Map szabályai szerint maradnak meg. Cserébe a JSON.stringify() nem ad hasznos JSON-reprezentációt egy Map-ről; ha szerializálni kell, külön alakításra lesz szükség.
Két API, két adatmodell
Object.groupBy(): szöveges, külső adatból származó vagy JSON-ba kerülő csoportnevekhez. Az eredményt null-prototípusú szótárként kezeld, ésObject.hasOwn()-t használj.Map.groupBy(): amikor a kulcs típusa és azonossága számít, például objektumokhoz vagy függvényekhez kötött kategóriáknál.- Mindkettő: a callback futásán kívül nem módosítja az input tömböt, de a csoportok elemei ugyanazok az objektumok maradnak.
Az Object.groupBy() visszatérési értékét ezért nem érdemes „még egy sima objektumként” kezelni. Pont az a haszna, hogy szótárként viselkedik, és nem ütközik az Object.prototype örökségével. Ha ezt már a típus és az első property-ellenőrzés megválasztásánál figyelembe vesszük, az API sokkal kiszámíthatóbb lesz.