← 中文资源

简体中文版本

按关键字段分组并输出 Map

按计算键或属性名把数组元素分到 Map,保留首次出现顺序并支持值投影。

阅读英文原文

功能说明

该示例把列表聚合为分组结构,返回 Map 而不是普通对象,因此键可以是数字、布尔值、对象引用或 Symbol,且不会与原型属性冲突。keyOf 接收元素与下标,valueOf 可把元素投影成统计值或精简对象。groupByProperty 是读取固定字段的便捷封装。适用于按状态归类订单、按作者聚合文章、构建索引等数据处理任务。

关键代码

实现只遍历一次,按首次出现的键顺序插入 Map;重复键直接追加到已有数组。桶数组在首次遇到键时创建,避免为不存在的分组预分配。keyOf 与 valueOf 都会收到原始下标,便于在投影时保留位置信息。groupByProperty 对 null 或 undefined 元素使用 undefined 作为键,而不是直接访问属性导致 TypeError,使脏数据仍可被归入单独分组。

使用注意

Map 的键按 SameValueZero 比较,NaN 被视为同一个键,对象键按引用而不是结构相等,因此内容相同的两个对象会落入不同分组。返回的桶数组是新建的,但元素仍是原引用,修改元素会影响原数组。分组顺序是首次出现顺序,不是键的字典序。需要普通对象输出时应在消费端显式转换,并注意特殊键名。

验证方式

按字符串字段分组,断言 Map 的键顺序与首次出现顺序一致;用同一对象引用作为键验证只分一组;测试数字、布尔值与 NaN 键;使用 valueOf 投影只保留 id,断言桶内元素被替换;传入含 null 的数组调用 groupByProperty,确认不会抛错且落到 undefined 分组;测试非数组输入抛 TypeError。

代码示例

export function groupBy(items, keyOf, valueOf = (item) => item) {
  if (!Array.isArray(items)) throw new TypeError("items must be an array");
  if (typeof keyOf !== "function") throw new TypeError("keyOf must be a function");
  if (typeof valueOf !== "function") throw new TypeError("valueOf must be a function");
  const groups = new Map();
  for (let index = 0; index < items.length; index += 1) {
    const item = items[index];
    const key = keyOf(item, index);
    let bucket = groups.get(key);
    if (!bucket) {
      bucket = [];
      groups.set(key, bucket);
    }
    bucket.push(valueOf(item, index));
  }
  return groups;
}

export function groupByProperty(items, property) {
  if (typeof property !== "string" || property.length === 0) {
    throw new TypeError("property must be a non-empty string");
  }
  return groupBy(items, (item) => (item == null ? undefined : item[property]));
}