- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
keyBy()是 collect.js 中用于将集合按指定键重新组织为「键 → 元素」映射的核心方法,非常适合在数据处理管线中把对象数组转换为可快速查找的字典结构。读完本文,你将掌握keyBy()的字符串键与回调两种用法、同键冲突时的去重语义、嵌套键取值原理,以及它与groupBy()的本质区别。
一、keyBy()是什么:从对象数组到键值映射
在 docs/api/keyBy.md 的官方定义中,keyBy()的作用是:将集合按给定的键(key)进行重新组织(keys the collection by the given key)。传入一个属性名字符串,或一个返回键值的回调函数,方法会遍历集合中的每一项,把提取出的键值作为新集合对象的属性名,把该项本身作为属性值。
其方法签名在 index.d.ts 中有明确的 TypeScript 定义:
keyBy<T, K>(key: keyof Item | K | Function): Collection<T>;也就是说,参数key既可以是集合元素上的某个属性名(keyof Item),也可以是任意自定义键值K,还可以是一个Function(回调函数),返回值始终是一个新的Collection<T>实例。调用后,你可以通过返回集合的all()方法(见 src/methods/all.js)拿到底层的纯对象。
二、基础用法一:以属性名字符串作为键
这是最直观的用法。假设有一个包含多件商品信息的数组,我们希望以manufacturer(制造商)作为新集合的键,文档中的完整示例如下:
const collection = collect([ { product: 'Chair', manufacturer: 'IKEA', }, { product: 'Desk', manufacturer: 'IKEA', }, { product: 'Chair', manufacturer: 'Herman Miller', }, ]); const keyed = collection.keyBy('manufacturer'); keyed.all(); // { // IKEA: { // product: 'Desk', // manufacturer: 'IKEA', // }, // 'Herman Miller': { // product: 'Chair', // manufacturer: 'Herman Miller', // }, // }注意输出结果中的一个关键细节:集合中有两件商品(Chair与Desk)的制造商都是IKEA,但最终结果中IKEA键只对应了Desk这一项。这正是keyBy()的核心语义——当多个元素拥有相同的键时,新集合中只保留最后出现的那个元素(only the last one will appear in the new collection)。
这一点同样被 test/methods/keyBy_test.js 中的测试用例明确验证:在第三个用例「should only keep one items per key」中,三位同属Liverpool FC的球员经过keyBy('club')后,结果只保留了排在最后的Mohamed Salah。
三、基础用法二:以回调函数的返回值作为键
除了字符串键,keyBy()还接受一个回调函数。回调接收当前项作为参数,返回的任意值将作为该项在新集合中的键。文档中的示例是把制造商名称转为大写后作为键:
const upperCased = collection.keyBy(item => item.manufacturer.toUpperCase()); upperCased.all(); // { // IKEA: { // product: 'Desk', // manufacturer: 'IKEA', // }, // 'HERMAN MILLER': { // product: 'Chair', // manufacturer: 'Herman Miller', // }, // }回调的灵活性远不止大小写转换:你可以拼接多个字段生成复合键(如item => \${item.category}-${item.brand}``)、对数值做区间归并、调用自定义函数等。只要回调能稳定返回一个可作为对象属性名的值,就能作为分键依据。
四、源码级实现:一次遍历完成重建
keyBy()的实现位于 src/methods/keyBy.js,完整代码如下:
'use strict'; const nestedValue = require('../helpers/nestedValue'); const { isFunction } = require('../helpers/is'); module.exports = function keyBy(key) { const collection = {}; if (isFunction(key)) { this.items.forEach((item) => { collection[key(item)] = item; }); } else { this.items.forEach((item) => { const keyValue = nestedValue(item, key); collection[keyValue || ''] = item; }); } return new this.constructor(collection); };从源码结构可以梳理出三个关键实现事实:
单次遍历、线性构建:方法通过
this.items.forEach(...)对集合做一次遍历,每次迭代把键 → 元素写入一个全新的普通对象collection。由于 JavaScript 对象属性赋值天然具备「后写覆盖先写」的特性,当遍历到同键的后续元素时,该键对应的值会被自动替换为最后一项——这正是「只保留最后一项」语义的底层来源,无需额外比较或去重逻辑。类型分支判断:方法借助 src/helpers/is.js 中导出的
isFunction(即typeof item === 'function')区分两种调用方式。传入函数则走回调分支,collection[key(item)] = item;否则走字符串键分支。返回新集合而非修改原集合:方法末尾通过
new this.constructor(collection)返回一个携带原构造函数类型的新Collection实例。因此调用keyBy()不会改变原集合的内容,这也在测试中被显式断言(如expect(collection.all()).to.eql(data);)。该方法在 src/index.js 中通过Collection.prototype.keyBy = require('./methods/keyBy');注册到原型上,可直接链式调用。
五、嵌套键支持:nestedValue如何解析a.b.c形式
keyBy()的字符串键分支并非简单地读取item[key],而是经由 src/helpers/nestedValue.js 取值:
module.exports = function nestedValue(mainObject, key) { try { return key.split('.').reduce((obj, property) => obj[property], mainObject); } catch (err) { // If we end up here, we're not working with an object, and @var mainObject is the value itself return mainObject; } };其原理是:先用key.split('.')把形如'club.name'的键拆成属性路径片段,再用Array.prototype.reduce沿路径逐层下钻取值。这带来的直接能力是——你可以用点号路径直接访问深层嵌套属性。
test/methods/keyBy_test.js 的第五个用例「should be able to use nested value as key」给出了实际场景:
const collection = collect([ { name: 'Virgil van Dijk', club: { name: 'Liverpool FC' }, }, { name: 'Darwin Núñez', club: { name: 'Liverpool FC' }, }, ]); const keyed = collection.keyBy('club.name'); expect(keyed.first()).to.eql({ name: 'Darwin Núñez', club: { name: 'Liverpool FC' }, });由于两位球员的club.name都是'Liverpool FC',按「同键只留最后一项」的规则,最终集合中该键对应的便是后出现的Darwin Núñez。同时nestedValue的try/catch保证了即使某一项不是对象(例如原始值是基本类型),也不会抛出异常,而是回退返回该项本身,提升了方法的健壮性。
六、边界情况:键不存在时统一归入空字符串键
当传入的字符串键在元素上不存在时,keyBy()并不会报错。源码中的collection[keyValue || ''] = item意味着:取到的键值为假值(如undefined、null、''、0等)时,该项会被归入空字符串键''。
test/methods/keyBy_test.js 的第四个用例「should key everything by an empty string if key does not exist」验证了这一点——对数据调用keyBy('xoxo')(一个不存在的键)后,所有元素都被键到了''之下,且由于后写覆盖,''键最终只保留最后一项:
const keyed = collection.keyBy('xoxo'); expect(keyed.all()).to.eql({ '': { id: 200, product: 'Chair', manufacturer: 'Herman Miller', price: '9990 NOK', }, });因此在实际业务中,如果要保证分键的准确性,建议先通过whereNotNull()等过滤方法剔除关键字段缺失的数据,或确保参与分键的字段一定存在。
七、与groupBy()的对比:什么时候用哪个
keyBy()与groupBy()很容易混淆,二者核心区别在于输出结构与去重行为:
| 维度 | keyBy() | groupBy() |
|---|---|---|
| 输出值 | 单个元素(同键只保留最后一项) | 元素数组(同键元素全部保留) |
| 典型用途 | 构建「id → 记录」字典,O(1) 查找 | 按分类聚合出组内列表 |
| 同键处理 | 覆盖为最后一项 | 追加到对应键的数组中 |
| 返回值结构 | 普通对象,值为原始项 | 对象,值为Collection实例 |
从 src/methods/groupBy.js 的实现可以看到,groupBy()在遇到新键时会先初始化collection[resolvedKey] = new this.constructor([]),随后把每一项push进该键对应的集合;而keyBy()则是直接整体覆盖赋值。二者对键不存在时的兜底策略也略有差异:groupBy()在nestedValue(item, key)取不到值(非 0)时同样归入''键。
选型建议:如果后续需要「按某字段直接定位单条记录」,用keyBy();如果需要「把同类的多条记录集中在一起做二次处理」,用groupBy()。
八、实战组合:把 keyBy 结果接入常用链路
由于keyBy()返回的是普通对象形态的新集合,你可以无缝衔接 collect.js 的其他方法,构建出典型的数据处理管线:
const users = collect([ { id: 1, name: 'Alice', dept: 'frontend' }, { id: 2, name: 'Bob', dept: 'backend' }, { id: 3, name: 'Carol', dept: 'frontend' }, ]); // 以 id 为键构建索引,再取出特定记录 const byId = users.keyBy('id'); byId.get(2); // { id: 2, name: 'Bob', dept: 'backend' } // 用回调生成复合键,实现多字段联合索引 const byDeptAndName = users.keyBy(user => `${user.dept}-${user.name}`); // 直接遍历键值映射 users.keyBy('id').each((user, id) => { // user 为元素,id 为新集合中的键 });配合all()、first()、get()等方法(分别见 src/methods/all.js、src/methods/first.js),可以在不引入任何第三方依赖的情况下,以近乎原生的性能完成「数组 → 索引表 → 定向查询」的转换。collect.js 本身在 package.json 中声明了零运行时依赖(dependencies为空),版本为4.36.1,可通过npm install collect.js安装后直接使用。
小结:keyBy()是一个「小而精」的方法——支持属性名字符串与回调两种分键方式、自动处理同键去重(保留最后一项)、支持点号路径访问嵌套属性、并对缺失键做了空字符串兜底。理解其源码实现(src/methods/keyBy.js)与测试用例(test/methods/keyBy_test.js),能帮助你在数据索引、去重归并、列表转字典等场景中写出更稳健的代码。
- 开发工具
【免费下载链接】collect.js
💎 Convenient and dependency free wrapper for working with arrays and objects
相关推荐
collect.js `mapToDictionary()` 方法全解析:将集合映射为字典分组结构
collect.js mapToDictionary 方法全解析:将集合映射为字典分组结构 mapToDictionary 是 collect.js 提供的一个
开发工具Pi MusicBox项目维护与社区参与:如何为开源项目贡献力量
Pi MusicBox项目维护与社区参与:如何为开源项目贡献力量 Pi MusicBox是一个将树莓派转变为易用音乐盒的开源项目,支持Spotify播放和Air
音视频物联网MyKeymap按键映射:为不同程序打造专属快捷键方案
MyKeymap按键映射:为不同程序打造专属快捷键方案 你是否曾经遇到过这样的困扰?在Photoshop中设置的快捷键在Word里完全失效,或者游戏中的按键组合
桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考