大家好,这里是大家的林语冰。
数组元素(item)分组可能是您已经反复实践的事情之一。您每次都会手写一个分组函数,或者复制粘贴 lodash 的 groupBy
函数。
好消息是: JS(JavaScript)现在支持分组方法,所以您不必再做"代码搬运工"了。Object.groupBy
和 Map.groupBy
是全新的方法,更易于分组,解放双手且降本增效。
免责声明
本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考,英文原味版请临幸 JAVASCRIPT IS GETTING ARRAY GROUPING METHODS。
直到现在的分组
假设您有一个代表人物的对象数组,并且您希望按年龄对它们进行分组。您可以使用 forEach
循环,如下所示:
js
const people = [
{ name: 'Alice', age: 28 },
{ name: 'Bob', age: 30 },
{ name: 'Eve', age: 28 }
]
const peopleByAge = {}
people.forEach(person => {
const age = person.age
if (!peopleByAge[age]) {
peopleByAge[age] = []
}
peopleByAge[age].push(person)
})
console.log(peopleByAge)
/*
{
"28": [{"name":"Alice","age":28}, {"name":"Eve","age":28}],
"30": [{"name":"Bob","age":30}]
}
*/
或者您可以使用 reduce
,如下所示:
js
const peopleByAge = people.reduce((acc, person) => {
const age = person.age
if (!acc[age]) {
acc[age] = []
}
acc[age].push(person)
return acc
}, {})
无论哪种方式,代码都有点猪头。您始终必须检查对象,查看分组键是否存在,如果不存在,那么就使用空数组创建它。然后,您可以将该元素推送到数组中。
使用 Object.groupBy 分组
使用全新的 Object.groupBy
方法,您可以一行搞定:
js
const peopleByAge = Object.groupBy(people, person => person.age)
草履虫都能学会!虽然有若干注意事项。
Object.groupBy
返回一个 null-prototype 对象(空原型对象)。这意味着,该对象不会从 Object.prototype
继承任何属性。这很美滋滋,因为这意味着,您不会意外覆盖 Object.prototype
的任何属性,但这也意味着,该对象没有您可能需要的任何方法,比如 hasOwnProperty
或 toString
。
js
const peopleByAge = Object.groupBy(people, person => person.age)
console.log(peopleByAge.hasOwnProperty('28'))
// TypeError: peopleByAge.hasOwnProperty 不是函数
传递给 Object.groupBy
的回调函数应返回 Symbol
或 string
。如果它返回任何其他内容,它将被强制转换为 string
。
在我们的栗子中,我们始终将 age
作为 number
返回,但在结果中,它被强制为 string
。尽管您仍然可以使用方括号表示法访问 number
属性,但也会将参数强制为 string
。
js
console.log(peopleByAge[28])
// => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]
console.log(peopleByAge['28'])
// => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]
使用 Map.groupBy 分组
Map.groupBy
几乎与 Object.groupBy
一毛一样,除了它返回了 Map
。这意味着,您可以使用所有常用的 Map
函数。这也意味着,您可以从回调函数返回任何类型的值。
js
const ceo = { name: 'Jamie', age: 40, reportsTo: null }
const manager = { name: 'Alice', age: 28, reportsTo: ceo }
const people = [
ceo,
manager,
{ name: 'Bob', age: 30, reportsTo: manager },
{ name: 'Eve', age: 28, reportsTo: ceo }
]
const peopleByManager = Map.groupBy(people, person => person.reportsTo)
在本例中,我们按人员向其报告对象对人员进行分组。请注意,要通过对象从 Map
中检索元素,对象必须具有相同的标识。
js
peopleByManager.get(ceo)
// => [{ name: "Alice", age: 28, reportsTo: ceo }, { name: "Eve", age: 28, reportsTo: ceo }]
peopleByManager.get({ name: 'Jamie', age: 40, reportsTo: null })
// => undefined
在上述栗子中,第二行使用一个看起来像 ceo
对象的对象,但它不是同一个对象,因此它不会从 Map
返回任何东东。若要从 Map
中成功检索元素,请确保保留要用作键的对象的引用。
何时可用?
这两种 groupBy
方法目前乃 TC39 提案 stage 3 的一部分。这意味着,它很有可能成为标准,因此,出现了若干实现。
Chrome 117 刚刚推行,支持这两种方法,Firefox 在 119 版本中发布了支持。Safari 以不同的名称实现了这些方法,我相信它们很快就会更新。由于这些方法在 Chrome 中,这意味着,它们已在 V8 中实现,因此下次 V8 更新时将在 Node 中可用。
为什么设计为静态方法?
您可能想知道为什么将其实现为 Object.groupBy
而不是 Array.prototype.groupBy
。根据该提案,有一个库对 Array.prototype
使用了不兼容 groupBy
的方法的"猴子补丁"(monkey patch)。在考虑新的 Web API 时,向后兼容十分重要。几年前,在尝试实现 Array.prototype.flatten
时,在一个 SmooshGate 的事件中强调了这一点。
幸运的是,使用静态方法实际上似乎更适合将来的可扩展性。当 Records 和 Tuples 提案实现时,我们可以添加一个 Record.groupBy
将数组分组为不可变记录的方法。
JS 正在填补空白
将元素分组在一起显然是我们作为开发者的必由之路。lodash.groupBy
目前 npm 周下载量高达 1_500_000
到 2_00_000
次。很高兴看到 JS 取而代之,让我们解放双手,降本增效。
现在,请到 Chrome 117 中亲自尝试这些新方法。语冰空谈终觉浅,极客撸码要宫刑。
您现在收看的是《前端翻译计划》,学废了的小伙伴可以订阅此专栏合集,我们每天佛系投稿,欢迎持续关注前端生态。谢谢大家的点赞,掰掰~