TC39JavaScript新語法Object.groupBy陣列處理

TC39 新語法深度解析(七):Object.groupBy — 內建分組,終於不需要 reduce 黑魔法

整理 ES2024 Object.groupBy 和 Map.groupBy(Stage 4)的用法,與 lodash _.groupBy 的比較,以及 Null 原型注意事項。

· 3 分鐘閱讀

這是 TC39 新語法深度解析系列的第七篇。陣列分組是超高頻操作——把訂單按狀態分組、把事件按日期分組、把使用者按角色分組。過去這需要 reduce 或 lodash _.groupBy。現在,Object.groupBy 讓這件事變成一行。


過去的做法

const orders = [
  { id: 1, status: 'pending', amount: 100 },
  { id: 2, status: 'completed', amount: 200 },
  { id: 3, status: 'pending', amount: 50 },
  { id: 4, status: 'refunded', amount: 200 },
];

// 過去:reduce 黑魔法
const byStatus = orders.reduce((groups, order) => {
  const key = order.status;
  groups[key] ??= [];
  groups[key].push(order);
  return groups;
}, {});
// { pending: [...], completed: [...], refunded: [...] }

這個 reduce 模式到處重複,現在可以一行:

const byStatus = Object.groupBy(orders, order => order.status);

Object.groupBy vs Map.groupBy

// Object.groupBy:返回普通物件(key 為字串)
const byStatus = Object.groupBy(orders, order => order.status);
// { pending: [...], completed: [...], refunded: [...] }

// Map.groupBy:返回 Map(key 可以是任何類型)
const byAmount = Map.groupBy(orders, order => order.amount > 100 ? 'expensive' : 'cheap');
// Map { 'expensive' => [...], 'cheap' => [...] }

什麼時候用哪個:

// 用 Object.groupBy:
// - key 是字串或可轉字串的值
// - 結果需要用 . 語法取值(group.pending)

// 用 Map.groupBy:
// - key 是數字、boolean、Date 等非字串
// - 需要 Map 的方法(has、forEach)
// - key 是 undefined 或 null

實際應用

事件日曆

const events = [
  { title: 'Team Meeting', date: '2026-03-19' },
  { title: 'Project Review', date: '2026-03-20' },
  { title: 'All Hands', date: '2026-03-19' },
  { title: 'Sprint Planning', date: '2026-03-22' },
];

// 按日期分組
const byDate = Object.groupBy(events, e => e.date);
// {
//   '2026-03-19': [Meeting, All Hands],
//   '2026-03-20': [Project Review],
//   '2026-03-22': [Sprint Planning]
// }

使用者角色管理

// 用 Map.groupBy(因為 role 是 string,通常用 Object)
// 但如果需要分組後「是否存在某個 role」
const roles = Object.groupBy(users, u => u.role);
if (roles.admin) {
  // 管理員數量
  console.log(roles.admin.length);
}

Null 原型 Gotcha

Object.groupBy 返回的物件沒有 prototypeObject.create(null)):

const result = Object.groupBy(orders, o => o.status);
console.log(result.hasOwnProperty === undefined); // true
console.log(result.constructor);  // undefined
console.log(result['pending']);  // 正常,但 Object 原型方法不存在

這個設計是故意的——防止 prototype 污染攻擊。如果需要原型方法:

const result = Object.groupBy(orders, o => o.status);
const safeResult = Object.assign(Object.create(null), result);
console.log(safeResult['pending']);  // 仍然正常

與 lodash _.groupBy 的比較

import { groupBy } from 'lodash';

// lodash
const byStatus = groupBy(orders, 'status');  // 指定 key 字串

// Object.groupBy
const byStatus = Object.groupBy(orders, o => o.status);  // 需要函式

Object.groupBy 比 lodash 更輕量(無需 library),但 lodash.groupBy 仍然支援字串作為 key selector(groupBy(arr, 'fieldName')),這在 lodash 中仍然有用。


瀏覽器支援

ES2024——所有主流瀏覽器與 Node.js 21+ 都已原生支援:

// 可以在產品環境直接使用
const grouped = Object.groupBy(data, item => item.category);

總結

Object.groupBy 是一個很小的語法,但它的價值是消除一個重複出現的樣板程式碼

// 從此告別
const grouped = items.reduce((acc, item) => {
  const key = item.field;
  (acc[key] ??= []).push(item);
  return acc;
}, {});

// 直接
const grouped = Object.groupBy(items, item => item.field);

這是 TC39 新語法深度解析系列的第七篇。下一篇:Pattern Matching — match 表達式讓條件判斷更有表達力。


延伸閱讀


本文基於 ES2024 Object.groupBy / Map.groupBy(Stage 4)整理。