core-js 中的 Array Grouping 提案:Object.groupBy 与 Map.groupBy 的完整解析

📅 发布时间:2026/9/12 0:07:23
core-js 中的 Array Grouping 提案:Object.groupBy 与 Map.groupBy 的完整解析
core-js 中的 Array Grouping 提案Object.groupBy 与 Map.groupBy 的完整解析【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js导读Array Grouping 提案为 JavaScript 引入了按回调函数返回值对可迭代对象进行分组的原生能力其最终形态落地为Object.groupBy与Map.groupBy两个静态方法而提案早期版本中的Array.prototype.groupBy/groupByToMap以及 TypedArray 版本则作为已废弃入口保留在esnext命名空间中。本文以仓库文档 docs/web/docs/features/proposals/array-grouping.md 为核心骨架结合 core-js 的源码实现、稳定版入口与单元测试完整讲解这两个 API 的签名、行为差异、底层实现原理以及在不同阶段入口下的使用方式帮助你准确地在旧引擎中落地分组逻辑。提案背景从 v1 原型方法到 v2 静态方法的演进Array Grouping 提案TC39proposal-array-grouping在演进过程中经历了两次 API 形态的重大调整v1早期草案在Array.prototype上添加groupBy与groupByToMap实例方法并为 TypedArray 添加groupBy。该阶段 API 最终被废弃Array.prototype.groupBy等并未进入 ECMA-262 标准。v2最终形态改为Object.groupBy与Map.groupBy两个静态方法已写入 ECMA-262属于标准 ES 特性。两者的核心区别在于返回结构Object.groupBy返回普通对象Map.groupBy返回Map实例。core-js 对这两个阶段都有支持最终标准版实现在es/stable/actual/full命名空间下以Object.groupBy、Map.groupBy形式提供如 stable/object/group-by.js、stable/map/group-by.js而废弃的 v1 原型方法则继续保留在esnext命名空间中方便需要兼容旧 API 的实验性代码。内置签名Built-ins signatures按关联文档给出的 TypeScript 签名两个方法的参数与返回结构如下class Object { static groupBy(items: Iterable, callbackfn: (value: any, index: number) key): { [key]: Arraymixed }; } class Map { static groupBy(items: Iterable, callbackfn: (value: any, index: number) key): Mapkey, Arraymixed; }两个方法都接收两个参数参数类型说明itemsIterable任意可迭代对象数组、字符串、Set、Map、生成器等也可以是类数组对象callbackfn(value, index) key对每个元素执行的分组回调接收元素值与递增下标返回该元素所属的分组键返回值差异是理解提案的关键Object.groupBy返回一个以null为原型的普通对象其原型为null避免hasOwnProperty、toString等继承属性干扰分组结果所有键会被隐式转换为字符串Symbol 键在转换时使用其描述文本。Map.groupBy返回一个Map实例键保持原始类型与引用相等性因此不会发生键的类型塌缩——例如数字1与字符串1会被视为不同分组对象与 Symbol 键也能被精确区分。行为差异与典型示例下面用同一份数据对比两种 API 的结果形态const numbers [1, 2, 3, 4, 5, 6]; // Object.groupBy —— 返回 null 原型对象键被字符串化 const byParity Object.groupBy(numbers, (n) (n % 2 0 ? even : odd)); // 结果{ even: [2, 4, 6], odd: [1, 3, 5] } // 注意Object.getPrototypeOf(byParity) null // Map.groupBy —— 返回 Map键保留原始类型 const byParityMap Map.groupBy(numbers, (n) (n % 2 0 ? even : odd)); // 结果Map { even [2, 4, 6], odd [1, 3, 5] }键类型不塌缩这一点在Map.groupBy中体现得最为直观// 数字 1 与字符串 1 在 Map.groupBy 中是两个分组 const grouped Map.groupBy([1, 1], (v) v); console.log(grouped.size); // 2 console.log(grouped.get(1)); // [1] console.log(grouped.get(1)); // [1] // 而在 Object.groupBy 中键会被字符串化两个值被合并到同一分组 const groupedObj Object.groupBy([1, 1], (v) v); console.log(Object.keys(groupedObj)); // [1] —— 键 1 覆盖了键 1回调函数同样接收(value, index)两个参数元素按可迭代顺序被依次处理且两种方法都会在实现内部对索引做安全整数上限校验见下文源码分析。源码级实现es.object.group-by 与 es.map.group-by仓库中两个标准实现的入口分别是 modules/es.object.group-by.js 与 modules/es.map.group-by.js两者通过$({ target: ..., stat: true, forced: ... })将方法挂载为静态方法且都设置了forced条件来决定是否覆盖原生实现。Object.groupBy 的实现要点// packages/core-js/modules/es.object.group-by.js结构摘录 $({ target: Object, stat: true, forced: DOES_NOT_WORK_WITH_PRIMITIVES }, { groupBy: function groupBy(items, callbackfn) { requireObjectCoercible(items); // 1. 强制可迭代对象可被读取 aCallable(callbackfn); // 2. 校验回调必须是可调用函数 var obj create(null); // 3. 创建 null 原型对象作为结果容器 var k 0; iterate(items, function (value) { doesNotExceedSafeInteger(k); // 4. 索引不超过安全整数上限 var key toPropertyKey(callbackfn(value, k)); // 5. 回调结果转属性键 if (key in obj) push(obj[key], value); else createProperty(obj, key, [value]); // 6. 首次出现时创建分组数组 }); return obj; } });实现细节可以从源码中逐条印证create(null)创建 null 原型对象源码中结果容器通过getBuiltIn(Object, create)创建为null原型对象与规范一致。测试 tests/unit-global/es.object.group-by.js 中也有assert.same(getPrototypeOf(groupBy([], it it)), null, null proto)的断言。toPropertyKey完成键字符串化回调返回值经由toPropertyKey转换为属性键字符串或 Symbol这与对象键被字符串化的行为一一对应。注释中的 IE 兼容细节源码注释指出in some IE versions,hasOwnPropertyreturns incorrect result on integer keys, but since its anullprototype object, we can safely usein——由于结果对象原型为null直接用in运算符判断键是否存在是安全的这也是为什么返回 null 原型对象不仅是规范要求还顺带规避了老 IE 上hasOwnProperty的已知缺陷。doesNotExceedSafeInteger(k)与iterateiterate是 core-js internals 中统一的迭代器封装见 internals/iterate.js负责兼容所有可迭代与类数组输入每次回调前都会做安全整数上限校验防止极端数据量下的索引溢出。Map.groupBy 的实现要点// packages/core-js/modules/es.map.group-by.js结构摘录 $({ target: Map, stat: true, forced: IS_PURE || DOES_NOT_WORK_WITH_PRIMITIVES }, { groupBy: function groupBy(items, callbackfn) { requireObjectCoercible(items); aCallable(callbackfn); var map new Map(); var k 0; iterate(items, function (value) { doesNotExceedSafeInteger(k); var key callbackfn(value, k); // 注意不经过 toPropertyKey if (!has(map, key)) set(map, key, [value]); else push(get(map, key), value); // 已有分组则追加元素 }); return map; } });与Object.groupBy的对照点键不做转换Map.groupBy中回调返回值直接作为 Map 键使用不经过toPropertyKey因此键保持原始类型与引用相等性这正是Map.groupBy支持对象键、Symbol 键且不塌缩1与1的原因。MapHelpers内部封装实现通过internals/map-helpers引入Map、has、get、set辅助函数保证在core-js-pure不污染全局命名空间的纯版本下也能正常工作。forced条件不同Map.groupBy的forced条件为IS_PURE || DOES_NOT_WORK_WITH_PRIMITIVES即纯版本下强制启用 polyfill同时修复了 WebKit 中原始字符串输入如Map.groupBy(ab, ...)无法正确分组的缺陷对应 WebKit bug 271524 场景。共享实现internals/array-group 与 esnext 旧入口v1 原型方法的共享内核废弃的 v1 API 并未被删除core-js 通过共享 helper 复用了它们的核心分组逻辑internals/array-group.jsArray.prototype.groupBy与%TypedArray%.prototype.groupBy的共享实现内部同样使用objectCreate(null)作为结果容器、toPropertyKey转换键、push追加元素代码中带有// TODO: Remove this block from core-js4注释表明该兼容逻辑计划在 core-js 4 中移除。internals/array-group-to-map.jsArray.prototype.groupByToMap的共享实现使用MapHelpers完成键值存取。对应挂载模块esnext.array.group-by.js ——Array.prototype.groupBy(callbackfn, thisArg)并通过addToUnscopables(groupBy)登记到Symbol.unscopables。esnext.array.group-by-to-map.js ——Array.prototype.groupByToMap(callbackfn, thisArg)挂载时指定name: groupToMap。esnext.typed-array.group-by.js ——%TypedArray%.prototype.groupBy通过exportTypedArrayMethod导出。这三个 esnext 模块统一由入口文件 proposals/array-grouping.js 加载require三个esnext.*模块文件首行注释同样标注了对应提案地址且带有// TODO: Remove from core-js4的清理标记。为什么 v1 与 v2 不能混用v1 的groupBy是实例方法arr.groupBy(fn)v2 的groupBy是静态方法Object.groupBy(arr, fn)。两者调用形态完全不同使用旧入口引入的Array.prototype.groupBy不会与标准的Object.groupBy冲突但新代码应统一使用 v2 静态 API仅在维护旧代码时通过core-js/proposals/array-grouping引入 v1 兼容实现。入口点Entry points与使用方式关联文档中明确给出的提案级入口点是core-js/proposals/array-grouping-v2该入口对应 v2 阶段提案加载Object.groupBy与Map.groupBy的实现。在应用入口处引入即可// 按提案入口加载实验性用途包含早期阶段提案 import core-js/proposals/array-grouping-v2; // 推荐直接使用 actual 命名空间含全部标准 ES web 标准特性 import core-js/actual; // 或只加载所需模块进一步减小体积 import core-js/actual/object/group-by; import core-js/actual/map/group-by;core-js 的入口点体系在 docs/web/docs/usage.md 中有完整说明其层级递进关系为full含早期提案→actual标准 ES web 标准 stage 3 提案→stable稳定 ES web 标准→es仅稳定 ES。由于Object.groupBy与Map.groupBy已是写入 ECMA-262 的标准特性它们可以直接从stable/es命名空间引用例如 stable/object/group-by.js 与 stable/map/group-by.js 都只是简单转发到es/对应实现// packages/core-js/stable/object/group-by.js use strict; var parent require(../../es/object/group-by); module.exports parent;es/下的实现则会在运行时检测原生Object.groupBy的存在与正确性通过fails探测原生可用时不再重复覆盖实现原生优先、缺失兜底的 polyfill 策略。如果希望完全不污染全局命名空间可以使用core-js-pure的纯版本入口如core-js-pure/actual/object/group-by。[!NOTE] 关联文档中给出的入口core-js/proposals/array-grouping-v2属于提案级入口适合实验与早期采用生产环境建议优先使用actual或更窄的按需模块入口。若使用 Vite、webpack 等打包器建议在应用入口文件顶部一次性加载所需 core-js 模块避免与业务代码中扩展原生对象的逻辑产生冲突详见 docs/web/docs/usage.md 中的相关警告。测试验证单元测试如何锁定行为仓库为两个静态方法都准备了完整的单元测试见 tests/unit-global/es.object.group-by.js。测试断言覆盖了规范要求的关键行为API 形态assert.isFunction(groupBy)、assert.arity(groupBy, 2)、assert.name(groupBy, groupBy)、assert.nonEnumerable(Object, groupBy)锁定方法的存在性、参数个数2、函数名与不可枚举属性。null 原型assert.same(getPrototypeOf(groupBy([], it it)), null, null proto)。分组正确性groupBy([1, 2, 1], it it ** 2)得到[[1, [1, 1]], [4, [2]]]验证重复元素并入同一分组、键被字符串化。输入多样性支持任意可迭代对象createIterable([1, 2])与字符串groupBy(qwe, it it)逐字符分组。回调参数断言回调恰好收到(value, index)两个参数index从 0 递增。Symbol 分组键测试使用Symbol(even)/Symbol(odd)作为分组键tests/unit-global/es.object.group-by.js#L27-L30附近验证 Symbol 键在Object.groupBy中的处理路径。Map.groupBy的同类测试位于 tests/unit-global/es.map.group-by.js。这些测试同时运行在unit-global全局命名空间版本与unit-pure纯版本两套测试目录中确保标准实现与不污染全局的core-js-pure版本行为一致。小结与选型建议对比维度Object.groupByMap.groupBy返回结构null 原型普通对象Map实例键处理经toPropertyKey字符串化原样保留支持任意键类型适合场景字符串分组、序列化输出、对象字面量消费需要对象/Symbol 键、避免键塌缩、依赖 Map 方法size、get、has实现位置es.object.group-by.jses.map.group-by.js稳定入口stable/object/group-by.jsstable/map/group-by.js在核心场景中需要将结果直接作为对象使用或序列化时选Object.groupBy需要保留键类型、使用引用相等性分组例如按对象分组、或需要Map的高效查找与size统计时选Map.groupBy。core-js 对两种形态均提供了标准、稳定、纯版本三档入口且源码级实现严格对齐 ECMA-262 的Object.groupBy/Map.groupBy规范语义配合fails探测与forced覆盖策略可以在任意老环境中得到与原生一致的行为。【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考