es-toolkit `mapValues` 深入解析:为原生 `Map` 提供不修改键的纯函数值变换
2026/9/17 1:33:29 网站建设 项目流程

es-toolkitmapValues深入解析:为原生Map提供不修改键的纯函数值变换

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

mapValues是 es-toolkit 在Map集合类型上提供的一个工具函数:它接收一个原生Map和一个生成新值的回调函数,返回一个键保持不变、值全部经过变换的全新Map,全程不修改原对象。本文以 docs/ja/reference/map/mapValues.md 为核心骨架,结合 源码实现 与 单元测试,完整讲解它的签名、用法、回调参数、类型行为、边界场景与底层原理,并对比对象版本的mapValues,让你可以在日常开发中直接、安全地使用它。

函数签名与核心行为

mapValues的完整 TypeScript 签名如下:

export function mapValues<K, V, R>( map: Map<K, V>, getNewValue: (value: V, key: K, object: Map<K, V>) => R ): Map<K, R>;

对应文档给出的简化形式:

const transformed = mapValues(map, getNewValue);
  • mapMap<K, V>:要被变换的Map
  • getNewValue(value: V, key: K, object: Map<K, V>) => R:从「值-键」对生成新值的回调函数,返回类型R可以与原值类型V不同。
  • 返回值(Map<K, R>:键与原Map完全相同、值被变换后的Map

三个核心行为可以总结为:键不变、值变换、原对象不修改。其中「返回全新 Map」这一点来自实现细节(见下文源码分析),也是它与forEach等原地遍历 API 的本质区别。

为什么只从es-toolkit/map导入?

文档中的::: info提示框明确指出:

此函数仅能从es-toolkit/map使用,以避免与其他集合类型的类似函数产生潜在冲突。

这并非营销话术,而是有事实依据的设计决策:es-toolkit 在object域下同样提供了名为mapValues的函数(实现见 src/object/mapValues.ts),用于将普通对象变换为新对象。两个函数同名、语义相近但针对不同的数据类型,因此被刻意隔离到不同的导出入口中。从 package.json 的exports字段可以看到./map./object是彼此独立的子路径;而在 src/map/index.ts 与 src/object/index.ts 中,它们分别通过各自目录的index.ts导出。这也解释了为什么 import 语句必须写成es-toolkit/map而不是从根入口导入——直接从根入口或es-toolkit/object导入的mapValues是针对普通对象的版本,对Map实例并不会按预期工作。

基础用法:值乘以倍数的经典示例

文档给出的第一个示例是最直观的用法——对所有值执行value * 2

import { mapValues } from 'es-toolkit/map'; const map = new Map([ ['a', 1], ['b', 2], ['c', 3], ]); const result = mapValues(map, value => value * 2); // 结果: // Map(3) { // 'a' => 2, // 'b' => 4, // 'c' => 6 // }

要点在于:result是一个全新的Map,其键'a''b''c'与输入完全一致,而值从1, 2, 3变换为2, 4, 6

回调函数:value、key、object 三个参数

getNewValue回调最多接收三个参数,文档中列出的完整签名是(value: V, key: K, object: Map<K, V>) => V(源码中返回值泛型为R,支持变换值类型)。实际开发中常用前两个:

import { mapValues } from 'es-toolkit/map'; // 值格式化:value 参数 const prices = new Map([ ['apple', 1.5], ['banana', 0.75], ['orange', 2.0], ]); const formatted = mapValues(prices, value => `$${value.toFixed(2)}`); // 结果: 值为 '$1.50'、'$0.75'、'$2.00' 的 Map // 基于键变换:同时使用 value 与 key 参数 const inventory = new Map([ ['premium_item', 10], ['standard_item', 20], ['basic_item', 30], ]); const adjusted = mapValues(inventory, (value, key) => key.startsWith('premium_') ? value * 1.5 : value ); // 结果: 值为 15、20、30 的 Map

第一个示例展示了值类型变换numberstring);第二个示例展示了依赖键的差异化处理——只有键以premium_开头的条目才应用* 1.5加成,其余保持原值。

第三个参数object原始 Map 本身。它由实现直接透传(见下文源码),可用于读取原始 Map 的规模或其他属性来影响变换结果。单元测试 src/map/mapValues.spec.ts 验证了这一行为:回调中originalMap与传入的map严格相等(toBe),且可以利用originalMap.size参与计算:

const result = mapValues(map, (value, key, originalMap) => { expect(originalMap).toBe(map); // 第三个参数就是原 Map 本身 return value + originalMap.size; });

源码级原理:循环 + 新 Map

实现非常精简,完整逻辑只有十几行(见 src/map/mapValues.ts):

export function mapValues<K, V, R>(map: Map<K, V>, getNewValue: (value: V, key: K, object: Map<K, V>) => R): Map<K, R> { const result = new Map<K, R>(); for (const [key, value] of map) { const newValue = getNewValue(value, key, map); result.set(key, newValue); } return result; }

从中可以提炼出三个实现级事实:

  1. 纯函数、无副作用:函数开头创建全新的result,整个遍历过程只对result调用set,从不写入或删除输入map的任何条目。因此无论回调做什么,原 Map 都不会被修改。
  2. 回调参数透传顺序for...of解构出[key, value],调用时按(value, key, map)顺序传入——值在前、键在后、原 Map 最后,与文档和签名保持一致。
  3. 保持插入顺序Map本身按插入顺序迭代,result.set也按相同顺序执行,因此输出 Map 的条目顺序与输入完全一致,这对依赖顺序的场景是重要的稳定性保证。

从复杂度看,这是典型的 O(n) 单次遍历实现,n 为 Map 条目数,没有嵌套循环或额外排序,性能开销极低——这也符合 es-toolkit 一贯的轻量、高性能定位。

边界场景与类型行为:测试用例验证

src/map/mapValues.spec.ts 中的测试覆盖了各类边界场景,可视为函数契约的权威描述:

场景测试位置行为验证
空 MapL60-L66返回空的new Map(),不抛错
单条目 MapL68-L74正常返回单条目的新 Map
原 Map 不被修改L76-L86调用前后Array.from(map.entries())完全一致
数字键L88-L104键类型K可以为number,值可任意变换
多种值类型L106-L124stringnumberbooleanobject混合值均可处理
值类型变换L126-L142numberstring等类型转换由泛型R支持

结合签名中的三个泛型参数(K键类型、V原值类型、R新值类型)可以确认:mapValues允许回调返回与原值完全不同的类型,例如把数字转成字符串、把对象序列化成文本,最终返回Map<K, R>。这种类型安全与map/filter等原生数组方法的泛型设计一脉相承。

与对象版mapValues的区别

es-toolkit 在 src/object/mapValues.ts 提供了同名函数,用于普通对象:

export function mapValues<T extends object, K extends keyof T, V>( object: T, getNewValue: (value: T[K], key: K, object: T) => V ): Record<K, V> { const result = {} as Record<K, V>; const keys = Object.keys(object); for (let i = 0; i < keys.length; i++) { const key = keys[i] as K; const value = object[key]; result[key] = getNewValue(value, key, object); } return result; }

两者的区别一目了然:

  • 数据类型不同Map版接收Map<K, V>并返回Map<K, R>;对象版接收普通对象并返回Record<K, V>
  • 遍历机制不同Map版使用for...of直接迭代Map条目;对象版使用Object.keys枚举自有可枚举属性。
  • 导入入口不同Map版从es-toolkit/map导入(src/map/index.ts),对象版从es-toolkit/object导入(src/object/index.ts)。

这种「同名不同域、按需导入」的设计正是文档提示框所强调的——在选择导入路径时务必确认你操作的是Map还是普通对象。

实战总结

mapValues适合一切「批量变换 Map 的值、但不想改动键、更不想污染原数据」的场景,例如价格格式化、状态映射、数据脱敏、DTO 转换等。核心记忆点有三条:

  1. es-toolkit/map导入,不要与对象版的mapValues混淆;
  2. 回调可拿到(value, key, object)三个参数,键与原始 Map 都可以参与新值的计算;
  3. 返回全新的Map<K, R>,原 Map 保持不变,值类型可以自由变换,空 Map、单条目、任意键类型都能安全处理。

如果想深入验证或扩展,可以继续阅读 mapValues 实现、mapValues 测试 以及同目录下的 Map 模块索引,其中还包含mapKeyskeyBycountBy等一批针对Map的实用函数。

【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询