Object.groupBy و Map.groupBy — تجميع المصفوفة من غير reduce

Object.groupBy و Map.groupBy بقوا Baseline Widely من مارس ٢٠٢٤ — جمّع عناصر المصفوفة بمفتاح من غير boilerplate الـ reduce. Chrome 117 · Firefox 119 · Safari 17.4 · Node 21.

كل مرة عايز تجمّع عناصر مصفوفة حسب خاصية — type، حالة، أو نطاق كمية — الناس بتروح لـ reduce وبتكتب accumulator و push و return. من مارس ٢٠٢٤ الموضوع بقى سطر واحد: Object.groupBy() و Map.groupBy(). الاتنين Baseline Widely available حسب MDN.

ليه reduce تقيل هنا؟

تجميع بـ reduce شكله تقريبًا كده:

const byType = inventory.reduce((acc, item) => {
  (acc[item.type] ??= []).push(item);
  return acc;
}, {});

شغّال، بس boilerplate، وسهل تغلط في الـ initial value أو تنسى الـ return. لو المفتاح مش string (كائن مثلًا) هتحتاج Map يدويًا وتلف أكتر.

Object.groupBy — مفاتيح نصية

Object.groupBy(items, callbackFn) بياخد iterable و callback بيرجّع قيمة تتحوّل لمفتاح خاصية (string أو symbol). النتيجة: كائن بدون prototype (null-prototype) كل خاصية فيه array للمجموعة.

من مثال MDN على inventory:

const inventory = [
  { name: "asparagus", type: "vegetables", quantity: 5 },
  { name: "bananas", type: "fruit", quantity: 0 },
  { name: "goat", type: "meat", quantity: 23 },
  { name: "cherries", type: "fruit", quantity: 5 },
  { name: "fish", type: "meat", quantity: 22 },
];

const byType = Object.groupBy(inventory, ({ type }) => type);
// vegetables: [...], fruit: [...], meat: [...]

const byStock = Object.groupBy(inventory, ({ quantity }) =>
  quantity > 5 ? "ok" : "restock",
);

العناصر في النتيجة هي نفس الـ references (مش deep copy) — لو عدّلت عنصر، هتشوفه في الأصل وفي المجموعة.

Map.groupBy — لو المفتاح كائن

لو محتاج تجمّع بمفتاح مش string — كائن بيتغيّر مع الوقت مثلًا — استخدم Map.groupBy(). الـ callback يرجع أي قيمة، والنتيجة Map؛ توصّل للمجموعة بـ get(key).

const restock = { restock: true };
const sufficient = { restock: false };
const result = Map.groupBy(inventory, ({ quantity }) =>
  quantity < 6 ? restock : sufficient,
);
console.log(result.get(restock));
// العناصر اللي quantity < 6

مهم من MDN: لازم تستخدم نفس كائن المفتاح اللي اتعمل بيه الـ Map. كائن جديد بنفس الشكل مش هيجيب المجموعة (undefined). تقدر تعدّل خصائص المفتاح الأصلي ويظل شغال.

إمتى Object وإمتى Map؟

  • مفاتيح string/symbol ثابتة تمثّل اسم مجموعة → Object.groupBy
  • مفاتيح object أو قيم عشوائية، خصوصًا لو الكائن بيتعدّل → Map.groupBy

اتنين كانوا في بعض المتصفحات على شكل Array.prototype.group() و groupToMap()؛ اتنقلوا لدوال static بسبب مشاكل توافق الويب. اعتمد على الأسماء الحالية.

التوافق (MDN BCD — Baseline Widely من مارس ٢٠٢٤)

حسب جداول التوافق وقت النشر:

  • Chrome / Edge: من 117
  • Firefox: من 119
  • Safari / iOS: من 17.4
  • Node.js: من 21.0.0
  • Deno: من 1.37 · Opera: من 103

يعني معظم الـ runtimes الحديثة جاهزة. لو لسه على Node 20 LTS أو متصفح أقدم، اتأكد أو حط شرط/polyfill.

رأيي

لو بتجمّع بعرض أو فلترة UI أو pipeline بيانات، Object.groupBy هو الـ default بدل reduce. احتفظ بـ Map.groupBy للحالات اللي المفتاح فيها كائن حقيقي مش اسم. متخترعش مفتاح من JSON.stringify(obj) لو هتحتاج ترجع لنفس الكائن بعدين — ساعتها Map أوضح.

المصادر

  • JavaScript
  • Object.groupBy
  • Map.groupBy
  • Baseline
  • Full Stack Tips
  • WebDev