- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
mapToDictionary()是 collect.js 提供的一个实用集合方法,它接收一个回调函数,对集合中的每个元素执行“字典映射”(dictionary map),最终把集合转换成一个以分组键为键、以值数组为值的普通 JavaScript 对象。本指南将带你掌握该方法的使用语法、回调返回值约定、源码级实现原理、与mapToGroups()、groupBy()、mapWithKeys()等相近方法的差异,以及如何在真实项目中用它完成数据分组统计。
方法速览
| 项目 | 说明 |
|---|---|
| 方法名 | mapToDictionary() |
| 所属库 | collect.js(便捷且零依赖的数组/对象操作封装库) |
| 核心能力 | 对集合执行字典映射,将每个元素映射为一个键值对并按键聚合成数组 |
| 回调返回值 | 一个二元数组(associative array),第一个元素为键(key),第二个元素为值(value) |
| 返回类型 | 新的 Collection 实例,其内部items为普通对象 |
| 文档位置 | docs/api/mapToDictionary.md |
| 源码位置 | src/methods/mapToDictionary.js |
| 测试位置 | test/methods/mapToDictionary_test.js |
基础用法:一个例子看懂 mapToDictionary
根据 docs/api/mapToDictionary.md 的官方说明,该方法的定义是:
Run a dictionary map over the items. The callback should return an associative array with a single key/value pair.
即:对集合中的每个元素运行一次字典映射;回调必须返回一个仅包含单个键值对的“关联数组”(在 JavaScript 中体现为[key, value]二元数组)。
官方示例代码如下:
const collection = collect([ { id: 1, name: 'a' }, { id: 2, name: 'b' }, { id: 3, name: 'c' }, { id: 4, name: 'b' }, ]); const groups = collection.mapToDictionary(item => [item.name, item.id]); groups.all(); // { // a: [1], // b: [2, 4], // c: [3], // }执行过程可以拆解为:
- 遍历集合中的 4 个元素;
- 对每个元素调用回调
item => [item.name, item.id],例如第一个元素{ id: 1, name: 'a' }会返回['a', 1]; - 以回调返回的键(
item.name)作为分组键,值(item.id)被追加到该键对应的数组中; - 由于
name为'b'的元素有两个(id 分别为 2 和 4),它们被聚合到同一个键'b'下,形成b: [2, 4]; - 最终得到
{ a: [1], b: [2, 4], c: [3] }。
需要注意的是:mapToDictionary()返回的是一个新的 Collection 实例,并不会修改原集合。这与 collect.js 中大多数方法(如 map()、mapToGroups() 等)保持一致的设计理念。
回调参数与返回值约定
回调签名
从 src/methods/mapToDictionary.js 的源码可以看到,回调会被传入两个参数:
module.exports = function mapToDictionary(fn) { const collection = {}; this.items.forEach((item, k) => { const [key, value] = fn(item, k); if (collection[key] === undefined) { collection[key] = [value]; } else { collection[key].push(value); } }); return new this.constructor(collection); };对应到回调:
- 第一个参数
item:当前遍历到的元素(当集合为对象时,是该键对应的值); - 第二个参数
k:当前元素的下标或键(源码中forEach的第二个参数,数组场景下为索引,对象场景下为键名)。
返回值必须是二元数组
回调必须返回形如[key, value]的数组。源码中直接使用解构赋值const [key, value] = fn(item, k);来取出键和值。这意味着:
- 数组第一个元素作为分组键(会被当作对象的属性名使用);
- 数组第二个元素作为被收集的值;
- 相同键的所有值会被累积到一个数组中,
undefined作为“键尚未出现”的哨兵值用于初始化数组(collection[key] === undefined时创建[value],否则执行push)。
这种设计让mapToDictionary()天然适合“一次遍历完成分组 + 收集值”的场景,无需额外的groupBy+pluck组合操作。
从源码看实现细节
分组键的类型转换
由于分组键最终被用作 JavaScript 对象的属性名,回调返回的键会被自动转换为字符串。例如在测试用例 test/methods/mapToDictionary_test.js 的“plain array”场景中:
it('should work plain array', () => { const collection = collect([1, 2, 3, 2, 1]); const groups = collection.mapToDictionary((item, key) => [item, key]); expect(groups.all()).to.eql({ 1: [0, 4], 2: [1, 3], 3: [2], }); });对纯数组[1, 2, 3, 2, 1]执行映射,回调返回[item, key],即把元素值当作键、把元素下标当作收集值,最终得到{ 1: [0, 4], 2: [1, 3], 3: [2] }。可以看到:
- 数值键
1、2、3在输出对象中被以字符串形式呈现; - 下标
0和4(值为 1 的两个位置)、1和3(值为 2 的两个位置)分别被聚合到对应键下; - 说明
mapToDictionary()对数组和对象集合均适用,且回调的第二个参数在不同集合形态下含义不同(索引 vs 键名)。
返回结构是普通对象而非嵌套 Collection
groupBy()方法(见 src/methods/groupBy.js)会把每个分组包装成独立的 Collection 实例(collection[resolvedKey] = new this.constructor([])),而mapToDictionary()返回的是普通对象:分组值只是朴素的 JavaScript 数组,没有再次封装。这一差异决定了二者的使用场景:mapToDictionary()适合直接消费(如渲染、序列化、传给图表库),groupBy()适合继续链式调用集合方法。
构造函数一致性
源码最后一行return new this.constructor(collection);使用了this.constructor而非硬编码的Collection。这与 collect.js 的整体实现一致——例如 src/index.js 中Collection.prototype.mapToDictionary = require('./methods/mapToDictionary');将方法挂载到原型上,this.constructor保证了方法在子类或自定义扩展场景下也能保持正确的构造链。
与相近方法的对比
collect.js 提供了多个功能相近的映射/分组方法,理解它们的差异有助于选对工具。以下是逐一对比:
mapToDictionary()vsmapToGroups()
mapToGroups()(源码见 src/methods/mapToGroups.js,文档见 docs/api/mapToGroups.md)的实现几乎与mapToDictionary()完全一致:
module.exports = function mapToGroups(fn) { const collection = {}; this.items.forEach((item, key) => { const [keyed, value] = fn(item, key); if (collection[keyed] === undefined) { collection[keyed] = [value]; } else { collection[keyed].push(value); } }); return new this.constructor(collection); };两者的差异仅体现在返回值语义上:
| 对比维度 | mapToDictionary() | mapToGroups() |
|---|---|---|
| 回调要求 | 返回[key, value]二元数组 | 返回[key, value]二元数组 |
| 分组值类型 | 普通数组(plain array) | 普通数组(plain array) |
| 实现代码 | 几乎相同(见 src/methods/mapToDictionary.js) | 几乎相同(见 src/methods/mapToGroups.js) |
| 命名寓意 | 强调“字典映射”,每个元素映射为一条键值对记录 | 强调“分组”,结果更贴合分组语义 |
两个方法在实际使用中几乎可以互换,选择哪个更多是语义表达上的偏好:mapToDictionary侧重“把元素映射成字典条目”,mapToGroups侧重“按回调结果分组”。在 README.md 中,两者分别对应#### mapToDictionary()(第 1497 行起)与#### mapToGroups()(第 1521 行起)两个独立小节。
mapToDictionary()vsgroupBy()
groupBy()(见 src/methods/groupBy.js)是 collect.js 中最常用的分组方法,两者的关键区别:
- 分组依据:
groupBy()可以直接传字符串键名(如groupBy('manufacturer')),也支持传入回调;mapToDictionary()只接受回调,且回调必须返回二元数组; - 分组值:
groupBy()保留的是原始元素本身,且每个分组被包装成新的 Collection(new this.constructor([]));mapToDictionary()收集的是回调返回的第二个元素(value),且分组值为普通数组; - 使用场景:需要继续对分组结果做链式集合操作(如
->map()、->sum())时优先groupBy();只需要把元素“压平”成{ key: [values] }结构直接消费时,mapToDictionary()更轻量。
groupBy() 官方文档 中也有相关提示:如果需要以普通对象形式分组(而不是嵌套 Collection),可以参考mapToGroups这一类方法。
mapToDictionary()vsmapWithKeys()
mapWithKeys()(见 src/methods/mapWithKeys.js、docs/api/mapWithKeys.md)同样要求回调返回[key, value],但语义完全不同:
mapWithKeys()是键值重映射:collection[keyed] = value,相同键的后一个值会覆盖前一个值,最终每个键只保留一个值;mapToDictionary()是分组聚合:相同键的值会累积成数组,不会互相覆盖。
典型的mapWithKeys()用法是把对象数组按唯一标识重排成查找表:
const keyed = collection.mapWithKeys(item => [item.email, item.name]); // { 'john@example.com': 'John', 'jane@example.com': 'Jane' }因此:需要“键 → 唯一值”的查找表时用mapWithKeys();需要“键 → 值列表”的分组结构时用mapToDictionary()。
实战应用:一次遍历完成分组统计
mapToDictionary()在真实项目中最典型的应用,是在不依赖第三方分组库的前提下,一次遍历完成“按某字段分组并提取另一字段”的统计需求。
场景一:按分类聚合商品 ID
const products = collect([ { id: 101, category: 'electronics', price: 199 }, { id: 102, category: 'books', price: 39 }, { id: 103, category: 'electronics', price: 299 }, { id: 104, category: 'books', price: 59 }, { id: 105, category: 'electronics', price: 129 }, ]); const byCategory = products.mapToDictionary(product => [product.category, product.id]); byCategory.all(); // { // electronics: [101, 103, 105], // books: [102, 104], // }场景二:利用回调第二个参数处理数组集合
当集合本身就是数组时,可以把下标或元素值作为键:
const nums = collect([3, 1, 4, 1, 5, 9, 2, 6, 5]); const positions = nums.mapToDictionary((value, index) => [value, index]); positions.all(); // { // 3: [0], // 1: [1, 3], // 4: [2], // 5: [4, 8], // 9: [5], // 2: [6], // 6: [7], // }这个例子演示了如何统计每个数字出现的所有下标位置,可用于去重分析、异常检测等场景。
场景三:读取结果并继续链式操作
由于mapToDictionary()返回的仍是 Collection 实例,可以继续调用其他方法:
const groups = collection.mapToDictionary(item => [item.name, item.id]); // 获取所有分组键 groups.keys().all(); // ['a', 'b', 'c'] // 获取纯数组形式的值 groups.values().all(); // [[1], [2, 4], [3]] // 转成 JSON groups.toJson();其中 keys()、values()(源码见 src/methods/values.js)与 toJson() 均为 collect.js 的常规方法,配合使用可以灵活地消费分组结果。
注意事项与边界情况
回调必须返回二元数组:如果回调返回值不可解构为
[key, value],源码中的解构赋值会得到undefined,导致collection[undefined]之类的意外结果。确保回调始终返回[key, value]形式。键会被强制转为字符串:对象属性名只能是字符串或 Symbol,因此回调返回的数字键会被自动字符串化。例如
{ 1: [0, 4] }中的键1实际是字符串'1'。不修改原集合:
mapToDictionary()返回新实例,原集合保持不变。这与 map() 文档 中“returns a new collection instance; it does not modify the collection it is called on”的说明一致。若需要原地修改,可考虑transform()。undefined作为哨兵值:源码用collection[key] === undefined判断键是否首次出现。因此如果回调返回的值为undefined,collection[key] = [undefined]后再次遇到同键会走push分支,行为是符合预期的。可配合
.all()读取:mapToDictionary()的结果需要通过 all()(源码见 src/methods/all.js)取出底层items才能得到纯对象,toArray()(见 src/methods/toArray.js)或toJson()也可作为输出手段。
如何运行验证
collect.js 仓库使用 Mocha + Chai 作为测试框架(见 package.json 的scripts.test字段:mocha test/tests.js)。mapToDictionary()的测试位于 test/methods/mapToDictionary_test.js,覆盖了两个典型场景:对象数组的字典分组与纯数组的“值作为键”分组。
在仓库根目录执行以下命令可运行全部测试(含mapToDictionary用例):
npm test若只想单独验证mapToDictionary方法,可结合 test/tests.js 支持的单测过滤机制运行:
npm run transpile && npx mocha test/tests.js --mapToDictionary(test/tests.js通过读取命令行最后一个参数并以--前缀识别要单独运行的方法测试文件,具体逻辑见 test/tests.js 第 10-27 行。)
小结
mapToDictionary()是一个轻量而高效的分组映射方法:它用“一次遍历 + 回调返回二元数组”的方式,把数组或对象集合转换成{ key: [values] }形式的字典结构,天然适合按字段分组、聚合收集值、快速构建统计表的场景。理解它与mapToGroups()、mapWithKeys()、groupBy()的差异,可以帮助你在不同需求下选择最合适的 collect.js 方法,写出更简洁、可读性更高的数据处理代码。
关联阅读:mapToGroups()、mapWithKeys()、groupBy()、map()、all()。
- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
相关推荐
JavaScript核心数据结构:数组、集合与映射
JavaScript核心数据结构:数组、集合与映射 本文全面介绍了JavaScript中三种核心数据结构:数组、集合(Set)与映射(Map)。详细讲解了数组的
教程示例工程Enzyme ShallowWrapper `.map(fn)` 详解:将浅渲染节点集合映射为任意数组
Enzyme ShallowWrapper .map fn 详解:将浅渲染节点集合映射为任意数组 导读 .map fn 是 enzyme ShallowWrap
测试前端collect.js集合运算与合并方法详解
collect.js集合运算与合并方法详解 本文详细介绍了collect.js库中集合运算与合并的核心方法,包括concat、merge、diff、inters
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考