☰
collect.js 的 groupBy() 方法:按指定键或回调对集合进行分组
2026/10/15 1:54:53 网站建设 项目流程
  • 开发工具

【免费下载链接】collect.js

💎 Convenient and dependency free wrapper for working with arrays and objects

项目地址:https://gitcode.com/gh_mirrors/co/collect.js
点击查看免费下载

groupBy() 是 collect.js 提供的高频分组工具,它能把集合中的元素按照某个键或自定义回调的计算结果拆分成“一组一组的集合”,用于数据统计、报表聚合、界面分类展示等场景。本文以 docs/api/groupBy.md 为核心,结合 groupBy 源码 与 groupBy 测试用例 深入讲解其用法、底层实现、边界行为以及与 mapToGroups()、keyBy() 的差异,读完即可在真实项目中熟练完成任意维度(含嵌套字段)的分组。

方法签名与分组语义

groupBy() 接收一个参数key,它既可以是字符串键名,也可以是回调函数:

groupBy<T, K>(key: ((item: Item, index?: number) => K) | keyof Item | K): Collection<T>;

(类型签名出自 index.d.ts)

其核心语义是:把集合中的每一项按解析出的分组键归入对应的桶,最终返回一个“以分组键为键、以 Collection 为值”的新集合——也就是说,返回值是一个“集合的集合”。原集合本身不会被修改,这一点与 collect.js 中其他方法保持一致的不可变风格。

按字符串键分组

最常见的用法是直接传入元素对象上的属性名。原文档给出了基于manufacturer(制造商)字段分组的示例:

const collection = collect([ { product: 'Chair', manufacturer: 'IKEA' }, { product: 'Desk', manufacturer: 'IKEA' }, { product: 'Chair', manufacturer: 'Herman Miller' }, ]); const grouped = collection.groupBy('manufacturer'); grouped.all(); // { // IKEA: Collection { items: [...] }, // 包含两条 IKEA 的产品 // 'Herman Miller': Collection { items: [...] } // 包含一条 Herman Miller 的产品 // }

分组完成后,grouped的每个值都是一个独立的 Collection 实例,可以继续调用 collect.js 的任何集合方法,例如对每个分组继续做count()、sum()、map()等操作。

按回调函数分组

除了字符串键,还可以传入回调函数。回调会收到当前项item和它的索引index两个参数,返回值就是该元素的分组键。原文档示例按厂商名称的前 3 个字母分组:

const grouped = collection.groupBy((item, key) => item.manufacturer.substring(0, 3)); grouped.all(); // { // IKE: Collection { items: [...] }, // IKEA 的两条产品 // Her: Collection { items: [...] }, // Herman Miller 的一条产品 // }

回调的灵活性让它非常适合处理“键名需要经过计算”的场景,例如:按价格区间分组、按时间戳归并到日期、按首字母分组、按字符串长度分组等。

源码实现原理

groupBy的实现非常精简,完整逻辑集中在 src/methods/groupBy.js:

module.exports = function groupBy(key) { const collection = {}; this.items.forEach((item, index) => { let resolvedKey; if (isFunction(key)) { resolvedKey = key(item, index); } else if (nestedValue(item, key) || nestedValue(item, key) === 0) { resolvedKey = nestedValue(item, key); } else { resolvedKey = ''; } if (collection[resolvedKey] === undefined) { collection[resolvedKey] = new this.constructor([]); } collection[resolvedKey].push(item); }); return new this.constructor(collection); };

逐行拆解可以看到四个关键设计点:

  1. 回调优先:通过 src/helpers/is.js 中的isFunction判断传入的是函数,若是则调用key(item, index),item与index与原生forEach的入参一一对应。
  2. 字符串键支持嵌套路径:字符串键统一交给 nestedValue 工具函数 解析,它会用.拆分路径并逐层取值(详见下文)。
  3. 0 值不落入空桶:注意条件写的是nestedValue(item, key) || nestedValue(item, key) === 0。这意味着当属性值等于0时,0会被当作合法的分组键保留,而不是被当作 falsy 值归入空字符串桶。这是一个容易踩坑的细节:数字 0、空字符串''、null等 falsy 值中,只有0被显式特殊处理。
  4. 缺失键兜底为空字符串:当字段不存在或值为 falsy(且不是 0)时,resolvedKey被设为'',这些元素会统一归入键为''的分组。

分组过程使用普通对象{}累积结果:首次遇到某个键时用new this.constructor([])创建新的 Collection,之后用push()追加元素;最终new this.constructor(collection)把键值对整体包装成新的集合返回。方法注册位于 src/index.js(Collection.prototype.groupBy = require('./methods/groupBy');)。

嵌套键分组(点号路径)

得益于nestedValue,字符串键支持点号语法,可以按对象内部的嵌套字段分组。groupBy 测试用例 中给出了按club.name分组的验证:

const collection = collect([ { name: 'Virgil van Dijk', club: { name: 'Liverpool FC' } }, { name: 'Darwin Núñez', club: { name: 'Liverpool FC' } }, ]); const grouped = collection.groupBy('club.name'); expect(grouped.first()).to.eql(collection); // 两名球员同属 Liverpool FC

nestedValue的核心实现只有一行:

key.split('.').reduce((obj, property) => obj[property], mainObject);

它把'club.name'拆成['club', 'name'],逐层向下取值;当对象层级不匹配抛出异常时,会通过 try/catch 兜底返回原值(此时说明该元素本身就是值而非对象)。因此groupBy('club.name')等价于对每个元素先做“按 club 再按 name 两级取值”,是处理嵌套数据最优雅的写法。

缺失键的行为:空字符串桶

当集合中部分元素缺少分组键时,它们不会被丢弃,而是统一归入键为''(空字符串)的分组。测试用例用groupBy('price')验证了这一点——products数组中 Herman Miller 的 Chair 没有price字段,最终结果中就会出现一个''分组:

const products = [ { product: 'Catalog', manufacturer: 'IKEA', price: 0 }, { product: 'Desk', manufacturer: 'IKEA', price: 60 }, { product: 'Chair', manufacturer: 'IKEA', price: 60 }, { product: 'Lamp', manufacturer: 'IKEA', price: 15 }, { product: 'Chair', manufacturer: 'Herman Miller' }, // 无 price 字段 ]; const grouped = collect(products).groupBy('price'); grouped.all(); // { // 0: Collection { items: [...] }, // price === 0 被保留为合法键 // 15: Collection { items: [...] }, // 60: Collection { items: [...] }, // '': Collection { items: [...] }, // 缺少 price 的元素归入这里 // }

由此可以总结分组键解析的完整规则:

传入 key 类型解析逻辑结果
函数调用key(item, index),以返回值作为键键由回调完全决定
字符串 + 元素含该字段取item[key](支持a.b.c嵌套)以字段值为键
字符串 + 字段值为 0取item[key]以0为键(非空字符串)
字符串 + 字段缺失或为 falsy兜底以''为键

返回“集合的集合”与链式操作

分组结果的特殊性在于:外层是 Collection,每个分组的值也是 Collection。测试用例明确验证了这一点:

const grouped = collection.groupBy('manufacturer'); grouped.first().all(); // 第一个分组(IKEA)的完整元素数组 grouped.last().all(); // 最后一个分组(Herman Miller)的完整元素数组

基于这一结构,你可以继续对分组结果做二次加工,例如:

const grouped = collect(products).groupBy('manufacturer'); // 统计每个分组内的元素数量 grouped.map(group => group.count()); // 只取出分组值,丢弃分组键(转成普通数组) grouped.values().all(); // 遍历每个分组 grouped.each((group, manufacturer) => { console.log(manufacturer, group.all()); });

与 mapToGroups() 的对比:何时该用谁

原文档专门给出了提示:如果希望得到“普通对象”形式的分组结果,而不是“集合的集合”,请使用 mapToGroups()。

mapToGroups的回调需要返回一个二元数组[key, value],实现位于 src/methods/mapToGroups.js,最终每个键对应的是普通数组:

const collection = collect([ { id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 3, name: 'C' }, { id: 4, name: 'B' }, ]); collection.mapToGroups((item, key) => [item.name, item.id]); // { // A: [1], // B: [2, 4], // C: [3], // }

两者的取舍可以这样概括:

  • groupBy():按键聚合元素,每个分组是完整的 Collection(元素保持原样),适合后续对分组继续做集合操作;
  • mapToGroups():回调显式指定“键 + 映射后的值”,每个分组是普通数组,适合把元素投影成简化结构(如上面的A: [1])。

与 keyBy() 的对比:合并还是保留全部

与 groupBy 容易混淆的还有 keyBy。二者的本质区别在于同键冲突时的处理:

  • groupBy():把同键的所有元素保留在同一个分组集合中(不丢数据);
  • keyBy():以键建立映射,同键时后出现的元素覆盖先出现的,最终每个键只保留一个元素(index.d.ts 中的注释明确说明 "If multiple items have the same key, only the last one will appear in the new collection")。

所以在做“按某字段把元素归并成一簇”时用 groupBy,在做“以某字段作为唯一索引查表”时用 keyBy。

测试验证与质量保障

groupBy的行为在 test/methods/groupBy_test.js 中有完整覆盖,共 5 组用例:

  1. 按给定字符串键分组,且断言原集合collection.all()保持不变(不可变性);
  2. 支持自定义回调分组(按厂商名前 3 个字符);
  3. 返回“集合的集合”,first()/last()可取出对应分组的完整元素;
  4. 缺失键时归入''分组,同时price: 0的元素被保留在0分组;
  5. 支持club.name这类嵌套键分组。

这些测试既是行为契约,也是使用时的最佳参考样例。运行方式遵循项目标准流程:

npm install npm test # 内部会先执行 babel transpile,再以 mocha 运行 test/tests.js

小结

groupBy()是 collect.js 中最具代表性的“重塑结构”方法之一:字符串键支持点号嵌套路径、回调支持任意计算逻辑、缺失键自动归入空字符串分组、0值被特殊保留,最终产出“集合的集合”以支持深度链式操作。配合 mapToGroups(普通对象结果)与keyBy()(唯一键映射)一起使用,可以覆盖绝大多数数据分组场景;其完整行为定义可在 docs/api/groupBy.md、src/methods/groupBy.js 与 test/methods/groupBy_test.js 中逐一核对。

  • 开发工具

【免费下载链接】collect.js

💎 Convenient and dependency free wrapper for working with arrays and objects

项目地址:https://gitcode.com/gh_mirrors/co/collect.js
点击查看免费下载
上一篇:gluestack-ui v4 样式体系实战:语义化令牌、tva 变体与暗黑模式
下一篇:Chinese Address Generator 项目教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询