- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
导读
本指南围绕 30 seconds of code 仓库中的经典片段 listify 展开,讲解如何借助Object.entries()与Object.fromEntries()这对互补的 API,配合自定义映射函数,在「字典型对象」与「对象数组」之间双向转换。读完本文,你将掌握listify与delistify两个工具的完整实现、边界行为与扩展思路,能够灵活处理"把对象摊平成数组"或"把数组收敛回对象"这类高频数据处理需求。
为什么需要把对象映射成数组
JavaScript 对象本质上是键值对(key-value pair)的集合,而数组则是有序的元素序列。在真实业务中,二者经常需要互相转换:
- 后端接口返回的是字典型对象,前端组件却希望以数组形式遍历渲染;
- 需要把对象的键作为新对象的一个字段(例如把
{ 'John': { age: 42 } }变成[{ name: 'John', age: 42 }]); - 反之,从表单、列表等数组形态的数据中重建字典型结构。
30 seconds of code 中的listify片段正是为这类场景设计的:用一个自定义映射函数,把对象的每个键值对转换成数组中的一个元素。
listify:把对象映射为对象数组
核心实现
最简单获取键值对的方式是Object.entries(),它返回一个形如[[key, value], ...]的二维数组。接着用Array.prototype.map()结合映射函数,即可把每个键值对转换成目标对象:
const listify = (obj, mapFn) => Object.entries(obj).map(([key, value]) => mapFn(key, value)); const people = [ { name: 'John', age: 42 }, { name: 'Adam', age: 39 }, ]; listify(people, (key, value) => ({ name: key, ...value })); // [ { name: 'John', age: 42 }, { name: 'Adam', age: 39 } ]逐行拆解
Object.entries(obj):把对象展开为键值对数组。注意上面示例传入的people是一个数组,此时Object.entries()会以索引作为键('0'、'1'),以元素作为值,因此示例能正确把每个对象摊平成{ name: key, ...value };[key, value]:在map回调中使用数组解构,直接取出键和值;mapFn(key, value):映射函数被调用时收到两个参数——键和值,其返回值会作为结果数组的元素;- 展开运算符
...value:把原对象的属性合并进新对象,name字段则由键提供。
映射函数的自由度
由于mapFn完全由调用方控制,listify可以产出任意形状的结果。例如只保留键:
listify({ a: 1, b: 2 }, key => key); // ['a', 'b']或者把键值对重新打包为元组:
listify({ a: 1, b: 2 }, (key, value) => [key, value]); // [['a', 1], ['b', 2]]这种"映射函数决定输出形状"的设计,使listify成为一个可复用的通用工具,而不是只能处理单一业务形态的硬编码函数。
delistify:把对象数组收敛回对象
核心实现
与listify相反,delistify使用Object.fromEntries()把「键值对数组」重新组装为对象。此时映射函数必须返回一个二元数组,其中第一个元素作为键、第二个元素作为值:
const delistify = (arr, mapFn) => Object.fromEntries(arr.map(mapFn)); const people = [ { name: 'John', age: 42 }, { name: 'Adam', age: 39 }, ]; delistify(people, (obj) => { const { name, ...rest } = obj; return [name, rest]; }); // { John: { age: 42 }, Adam: { age: 39 } }映射函数的返回值约定
delistify的mapFn与listify的有一个关键差异:它必须返回长度为 2 的数组(二元组)。上例中:
const { name, ...rest } = obj;:解构出name字段作为新键,其余属性收集进rest;return [name, rest];:返回[键, 值]二元组,交给Object.fromEntries()组装。
仓库中另一个片段 map-array-to-object 也遵循同一约定——它用Array.prototype.map()把数组元素映射为二元组,再交给Object.fromEntries()生成对象,印证了"fromEntries接受二元组数组"这一通用模式:
const mapObject = (arr, fn) => Object.fromEntries(arr.map((el, i, arr) => [el, fn(el, i, arr)])); mapObject([1, 2, 3], a => a * a); // { 1: 1, 2: 4, 3: 9 }一对互补的底层 API
listify与delistify之所以能实现"互为逆操作",根源在于 JavaScript 原生 API 的设计恰好对称:
| API | 输入 | 输出 | 在本文中的角色 |
|---|---|---|---|
Object.entries(obj) | 对象 | [[key, value], ...]二维数组 | listify的起点 |
Object.fromEntries(pairs) | [[key, value], ...]二维数组 | 对象 | delistify的终点 |
Array.prototype.map(fn) | 数组 | 新数组 | 两者的转换载体 |
更直观地说:Object.entries()相当于"把对象列表化",Object.fromEntries()相当于"把列表对象化",二者天然可逆。仓库中的 convert-map-to-object 片段也利用了这对 API 在Map与普通对象之间互相转换,可见其应用范围远不止对象与数组:
const mapToObject = map => Object.fromEntries(map.entries()); const objectToMap = obj => new Map(Object.entries(obj));实战:常见数据形态转换
场景一:字典对象 → 带键字段的对象数组
接口返回{ 'apple': { price: 5 }, 'banana': { price: 3 } },需要渲染成表格行:
const inventory = { apple: { price: 5 }, banana: { price: 3 } }; listify(inventory, (key, value) => ({ name: key, ...value })); // [ { name: 'apple', price: 5 }, { name: 'banana', price: 3 } ]场景二:对象数组 → 以某字段为键的字典
前端表单提交的重复行数据,需要按主键去重或建索引:
const rows = [ { id: 'a1', count: 2 }, { id: 'b2', count: 5 }, ]; delistify(rows, ({ id, ...rest }) => [id, rest]); // { a1: { count: 2 }, b2: { count: 5 } }场景三:与键变换/键值反转配合使用
对象转换在仓库中是一个完整家族,listify常常与它们组合出现:
- transform-object-keys 展示了如何用
Object.keys()+reduce()映射、重命名甚至递归深层次变换对象键; - invert-key-values 用
Object.entries()+Array.prototype.map()把键值对反转后交给Object.fromEntries()重建对象,与listify使用完全相同的 API 链路:const invertKeyValues = obj => Object.fromEntries(Object.entries(obj).map(entry => entry.reverse())); invertKeyValues({ a: 1, b: 2, c: 3 }); // { 1: 'a', 2: 'b', 3: 'c' } - object-array-proxy 则展示了更高阶的思路——用
Proxy把对象包装成拥有map、filter、length、Symbol.iterator等数组行为的"键控数组",满足"不想修改原对象又想要数组式 API"的诉求。
可以看到,无论工具形态如何变化,底层都离不开Object.entries()/Object.keys()/Object.values()与Array.prototype.map()的组合。
边界与注意事项
- 键的类型会变成字符串:
Object.entries()产出的键恒为字符串(数字索引也会被字符串化),因此listify展开数组时得到的是'0'、'1'这样的键名; - 重复键会被覆盖:
delistify依赖Object.fromEntries(),如果映射出的键重复,后者会覆盖前者。若需要保留重复键,可参照 invert-key-values 中"遇到重复值收集进数组"的思路; - 映射函数签名要对称:
listify的mapFn(key, value)收到两个参数,delistify的mapFn(obj)收到一个对象且必须返回二元组。二者签名不同,混用时务必注意; - 空对象/空数组:
Object.entries({})返回空数组,map后仍是空数组;Object.fromEntries([])返回{},两个工具对空输入均能优雅降级,不会报错。
小结
listify与delistify是 30 seconds of code 中一对小巧而对称的工具:前者用Object.entries()+Array.prototype.map()把字典型对象摊平为对象数组,后者用Array.prototype.map()+Object.fromEntries()把对象数组收敛回字典。核心要点可以概括为三句话:
- 展开:
Object.entries()得到键值对数组,map自定义输出形状; - 收敛:映射函数返回
[key, value]二元组,交给Object.fromEntries()重建对象; - 扩展:这对 API 还可用于
Map转换、键值反转、键变换等更多对象操作,是 JavaScript 对象处理工具箱中的基础件。
完整的源码与示例位于 content/snippets/js/s/listify.md,可结合 map-array-to-object、convert-map-to-object、transform-object-keys 等相邻片段进一步研读。
- 教程
- 文档
【免费下载链接】30-seconds-of-code
Coding articles to level up your development skills
相关推荐
30 seconds of code 实战:JavaScript Map 与普通对象的双向转换
30 seconds of code 实战:JavaScript Map 与普通对象的双向转换 在 Map 与普通 JavaScript 对象(plain ob
教程文档CANN ops-math 算子开发指南:aclnnLog 与 aclnnInplaceLog 自然对数算子接口详解与实战
CANN ops math 算子开发指南:aclnnLog 与 aclnnInplaceLog 自然对数算子接口详解与实战 导读 本文基于 CANN ops m
教程文档30 seconds of code 实战:在 JavaScript 中压缩数组与对象(Compact Array or Object)
30 seconds of code 实战:在 JavaScript 中压缩数组与对象(Compact Array or Object) 在 JavaScrip
教程文档
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考