前言

group by 是一个很常见的功能,但 JS 却没有 build-in 的方法,一直到 es2024 才有 Object.groupBy (前生是 Array.prototype.group) 和 Map.groupBy (前生是 Array.prototype.groupToMap)。

目前所有 modern browser 都支持了这个功能。

如果想兼容 IOS 17.4 以下,可以使用 core-js polyfill

参考

ECMAScript 2023将新增的九个数组方法

Stack Overflow – Most efficient method to groupby on an array of objects (用 reduce 实现的 group by, 也是最 popular 的方案)

Setup Polyfill

我需要兼容 IOS,所以例子我就搭配 core-js 呗。

yarn add core-js

然后 import

import 'core-js/actual/map/group-by';
import 'core-js/actual/object/group-by';

这样就可以了

Object.groupBy

Object.groupBy 是一个静态方法,它的调用接口长这样

groupBy<K extends string | number | symbol, T>(
items: Iterable<T>,
keySelector: (item: T, index: number) => K,
): Partial<Record<K, T[]>>;

groupBy 的用法很简单, 给一个 item array,配上一个 select key 函数,返回一个 key,它会把相同 key 的 item group 在一起。

最终返回一个对象 (non-prototype object),对象的 key 就是 group by 的 key,value 则是相同 key 的所有 items。

注:key 的类型必须是 string | symbol | number,其它的最好不要,如果 select key 函数返回的不是 string | symbol 会自动被强转成 string。

例子:group by name

const items = [
{ name: 'Derrick', age: 1 },
{ name: 'Peter', age: 1 },
{ name: 'Derrick', age: 2 },
{ name: 'Peter', age: 2 },
];
const result = Object.groupBy(items, item => item.name);
console.log(JSON.stringify(result,

结果

Map.groupBy

groupBy<K, T>(
items: Iterable<T>,
keySelector: (item: T, index: number) => K,
): Map<K, T[]>;

它和 Object.groupBy 差不多,只是 group key 不强制要求必须是 string | number | symbol,group key 可以是任何类型,而且不会自动强转成 string。

它之所以可以是任何类型是因为 Map.groupBy 返回的结果不是 Object,而是 Map,Map 的 key 可以是任何类型。

const items = [
{ name: 'Derrick', age: 1 },
{ name: 'Peter', age: 1 },
{ name: 'Derrick', age: 2 },
{ name: 'Peter', age: 2 },
];
const result = Map.groupBy(items, item => item.name);
for (const [key, value] of result) {
// 1. ['Derrick', [{ name: 'Derrick', age: 1 }, { name: 'Derrick', age: 2 }]]
// 2. ['Peter', [{ name: 'Peter', age: 1 }, { name: 'Peter', age: 2 }]]
console.log([key, value]);
}

How It Group?

group by 的关键之一就是 group key 的 comparison。

比如我 fetch 一些资料,然后想 group by Date。

如果使用 Object.groupBy 的话,它会先把 Date 转成 string 然后放入对象的 key (利用对象 key unique 特性来 group,可以理解为 key1 === key2 就 group 在一起)

如果是 Map.groupBy 则是放入 Map 的 Key,这里和 Object.groupBy 有一个微小的区别,它不会把 Date 强转成 string,所以 key1 === key2 对比的是 Date object 而不是 Date string。

通常,我们的直觉会认为是,相同的 date time value group 在一起,而不是相同指针 group 在一起,这样用 Map.groupBy 的结果就是错误的。

const today1 = new Date(2023, 0, 26);
const today2 = new Date(2023, 0, 26);
const items = [
{ date: today1, age: 1 },
{ date: today1, age: 1 },
{ date: today2, age: 2 },
];
const result1 = Object.groupBy(items, item => item.date as unknown as string);
console.log(Object.keys(result1).length); // 1 const result2 = Map.groupBy(items, item => item.date);
console.log([...result2.keys()].length); // 2

所以在使用 Object.groupBy 或 Map.groupBy 时,一定要注意 group key 的类型。

How It Order?

上面提到了,Object.groupBy 返回的是 object,而 object 的 key 是很难确保顺序的,参考: Object.keys(..)对象属性的顺序? (number first, order by create, symbol last)

const people = [
{ name: 'derrick', age: 11 },
{ name: 'derrick', age: 15 },
{ name: '1148', age: 22 },
{ name: '1148', age: 18 },
];
console.log(Object.keys(Object.groupBy(people, person => person.name))); // ['1148', 'derrick']
console.log([...Map.groupBy(people, person => person.name).keys()]); // ['derrick', '1148']

Object.keys 的顺序是数字优先的 (哪怕是 string number 也同样优先...), 所以,如果想依据 array 原本的顺序那么请尽可能使用 Map.groupBy。

Multiple Group Key

C# LINQ GroupBy 支持 multiple group key

items.GroupBy(item => new { item.Name, item.Age }).ToList()

但 JS 没有这个功能,勉强要实现的话可以用 JSON.stringify,只是性能差又不优雅...

分享我以前写的 group by multiple key

function groupByMultipleKey<TItem, TKeys extends unknown[]>(
items: TItem[],
keySelector: (item: TItem, index: number) => [...TKeys],
): Map<[...TKeys], TItem[]> {
const resultMap = new Map<[...TKeys], TItem[]>(); for (let index = 0; index < items.length; index++) {
const item = items[index]; const itemKeys = keySelector(item, index); const existedKey = [...resultMap.keys()].find(resultKeys =>
resultKeys.every((resultKey, index) => isSameKey(resultKey, itemKeys[index])),
); if (existedKey !== undefined) {
resultMap.get(existedKey)!.push(item);
} else {
resultMap.set(itemKeys, [item]);
}
}
return resultMap; function isSameKey(key1: unknown, key2: unknown): boolean {
if (key1 === key2) return true;
if (Number.isNaN(key1) && Number.isNaN(key2)) return true;
if (key1 instanceof Date && key2 instanceof Date) {
return key1.getTime() === key2.getTime();
}
return false;
}
}

JavaScript – Object.groupBy & Map.groupBy的更多相关文章

  1. ES6新特性:Javascript中的Map和WeakMap对象

    Map对象 Map对象是一种有对应 键/值 对的对象, JS的Object也是 键/值 对的对象 : ES6中Map相对于Object对象有几个区别: 1:Object对象有原型, 也就是说他有默认的 ...

  2. JavaScript中的Map

    1.首先,在新版本的浏览器中,已经实现了对Map的原生支持:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Glob ...

  3. 由实现JavaScript中的Map想到的

    项目中要用到JavaScript中的Map数据类型,它不像JDK那样有自带的,怎么办?搜了找到一个不错的(http://darkmasky.iteye.com/blog/454749).用这个可以满足 ...

  4. 【EatBook】-NO.1.EatBook.1.JavaData.1.001-《JSON 必知必会-Introduction to JavaScript Object Notation》-

    1.0.0 Summary Tittle:[EatBook]-NO.1.EatBook.1.JavaData.1.001-<JSON 必知必会-Introduction to JavaScrip ...

  5. JavaScript 中的 Map

    很多编程语言中都有类似Map这种 键-值对 的数据结构. 可惜,JavaScript没有. 幸运的是,可以自己构建一个Map对象. 对象的定义 <script type="text/j ...

  6. JSON,全称:JavaScript Object Notation,作为一个常见的轻量级的数据交换格

    JSON,全称:JavaScript Object Notation,作为一个常见的轻量级的数据交换格式,应该在一个程序员的开发生涯中是常接触的.简洁和清晰的层次结构使得 JSON 成为理想的数据交换 ...

  7. JSON: JavaScript Object Notation

    JSON是JavaScript Object Notation 的缩写,是JS提供的一种数据交换格式.1) JSON对象本质上就是一个JS对象,但是这个对象比较特殊,它可以直接转换为字符串,在不同语言 ...

  8. 【java】之3种方式实现Object和Map之间的转换

    利用commons.BeanUtils实现Obj和Map之间转换,这种是最简单,也是最经常用的 public static Object mapToObject(Map<String, Obje ...

  9. Javascript Object、Function对象

    1.Object对象 原型对象 原型是对象的一个属性,也就是prototype属性,每个对象都有这个内部属性,而且他本身也是一个对象. <script type="text/javas ...

  10. XML.ObjTree -- XML source code from/to JavaScript object like E4X

    转载于:http://www.kawa.net/works/js/xml/objtree-try-e.html // ========================================= ...

随机推荐

  1. tp5框架No input file specified

    最近从网上下载了一个项目,本地搭建好环境.访问页面出现No input file specified. 这个问题之前就遇到过,是因为权限的问题,导致nginx无法解析php文件,这次有点不一样所以记录 ...

  2. oeasy教您玩转vim - 1 - # 存活下来 🥊

    存活下来 更新 apt 源,升级 vim vim 是什么 vim 是类 unix 系统上的一个文本编辑神器,在 Linux 系统环境中也被许多程序员使用,书写程序和文档. 我们本次课程将围绕 Vim ...

  3. Excel VBA编程常用语句300句

    定制模块行为 1. Option Explicit '强制对模块内所有变量进行声明 Option Private Module '标记模块为私有,仅对同一工程中其它模块有用,在宏对话框中不显示 Opt ...

  4. JavaScript 监听组合按键

    JavaScript监听组合按键   by:授客 QQ:1033553122 1.   思路 如图,通过监听并打印键盘keydown事件,得到图示内容,观察发现, 当按下的组合键包含Ctrl键时,ct ...

  5. PHP转Go系列 | 推荐一个强大的Go语言工具函数库

    大家好,我是码农先森. 从 PHP 转到 Go 的朋友,常常会因为没有便捷的工具函数而感到苦恼.PHP 写的多了就会形成路径依赖,在写 Go 的时候时不时就会想到 PHP 强大的数组函数.当然写 Go ...

  6. 快速将headers转字典

    使用Headers插件完成快捷操作 在pycharm的Preferences-Plugins-Marketplace下搜索Headers install安装.apply应用,ok确定 接下来只要复制相 ...

  7. 系动词&使役动词

    系动词 系动词的作用就是赋值 I am a rabbit 把 a rabbit赋值给i我 我是一只兔子 The rabbit is smart 这兔子是聪明的 smart赋值给兔子 系动词连系的方式, ...

  8. 【DataBase】XueSQL Training

    地址: http://xuesql.cn/ Lesson0 -- 认识SQL -- [初体验]这是第一题,请你先将左侧的输入框里的内容清空,然后请输入下面的SQL,您将看到所有电影标题: SELECT ...

  9. signal-slot:python版本的多进程通信的信号与槽机制(编程模式)的库(library) —— 强化学习ppo算法库sample-factory的多进程包装器,实现类似Qt的多进程编程模式(信号与槽机制) —— python3.12版本下成功通过测试

    什么是 Qt signal-slot库项目地址: https://github.com/alex-petrenko/signal-slot 该库实现的主要原理: 要注意这个项目的library只是对原 ...

  10. gym.ActionWrapper使用时的注意点——step函数可以覆盖observation函数

    本文说的这个gym.ActionWrapper继承类的问题和gym.ObservationWrapper继承类的问题性质是一样的,具体看: gym.ObservationWrapper使用时的注意点- ...