对象的映射函数(而非数组)
技术背景
在 JavaScript 中,Array
原型上有 map
方法,但 Object
对象本身没有原生的 map
方法。不过在实际开发中,我们可能需要对对象的键值对进行映射操作,将对象的每个值进行转换,生成一个新的对象。本文将介绍多种实现对象映射功能的方法。
实现步骤
直接修改原对象
可以使用 Object.keys
和 forEach
或者 for...in
循环来遍历对象并修改其值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var myObject = { 'a': 1, 'b': 2, 'c': 3 }; Object.keys(myObject).forEach(function(key, index) { myObject[key] *= 2; }); console.log(myObject);
var myObject = { 'a': 1, 'b': 2, 'c': 3 }; for (var key in myObject) { if (myObject.hasOwnProperty(key)) { myObject[key] *= 2; } } console.log(myObject);
|
返回新对象
以下几种方法可以在不修改原对象的情况下返回一个新对象:
使用 reduce
方法
1 2 3 4 5 6 7 8 9 10 11 12
| var myObject = { 'a': 1, 'b': 2, 'c': 3 }; function objectMap(object, mapFn) { return Object.keys(object).reduce(function(result, key) { result[key] = mapFn(object[key]); return result; }, {}); } var newObject = objectMap(myObject, function(value) { return value * 2; }); console.log(newObject); console.log(myObject);
|
使用 ES6 特性
1 2 3 4 5 6 7 8
| const objectMap = (obj, fn) => Object.fromEntries( Object.entries(obj).map( ([k, v], i) => [k, fn(v, k, i)] ) ); const myObject = { a: 1, b: 2, c: 3 }; console.log(objectMap(myObject, v => 2 * v));
|
ES10 / ES2019 单行实现
1
| let newObj = Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, v * v]));
|
递归处理嵌套对象
1 2 3 4 5 6 7 8
| function objMap(obj, func) { return Object.fromEntries( Object.entries(obj).map(([k, v]) => [k, v === Object(v) ? objMap(v, func) : func(v)] ) ); } let mappedObj = objMap(obj, (x) => x * x);
|
处理继承属性
当需要处理继承属性时,Object.keys
和 Object.entries
无法获取原型链上的属性,此时可以使用 for...in
循环:
1 2 3 4 5 6 7
| const obj1 = { 'a': 1, 'b': 2, 'c': 3}; const obj2 = Object.create(obj1); console.log(Object.keys(obj2)); console.log(Object.entries(obj2)); for (let key in obj2) { console.log(key); }
|
核心代码
以下是一个通用的对象映射函数:
1 2 3 4 5 6
| const objectMap = (obj, fn) => Object.fromEntries( Object.entries(obj).map( ([k, v], i) => [k, fn(v, k, i)] ) );
|
最佳实践
- 当需要修改原对象时,使用
Object.keys
和 forEach
或 for...in
循环。 - 当需要返回新对象且不修改原对象时,推荐使用
Object.fromEntries
和 Object.entries
的组合,这是 ES6 及以上版本中较为简洁和现代的实现方式。 - 处理嵌套对象时,使用递归函数确保所有嵌套层次的对象都能被正确映射。
常见问题
如何处理继承属性?
使用 for...in
循环,它会遍历对象自身及其原型链上的可枚举属性。但需要注意使用 hasOwnProperty
来过滤掉原型链上的属性,除非你确实需要处理它们。
如何实现异步映射?
可以使用 p-map
库来实现异步映射,示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import pMap from "p-map";
export const objectMapAsync = async <InputType, ResultType>( object: { [s: string]: InputType } | ArrayLike<InputType>, mapper: (input: InputType, key: string, index: number) => Promise<ResultType> ): Promise<{ [k: string]: ResultType; }> => { const mappedTuples = await pMap( Object.entries(object), async ([key, value], index) => { const result = await mapper(value, key, index); return [key, result]; } );
return Object.fromEntries(mappedTuples); };
|
如何同时映射键和值?
可以自定义函数来实现同时映射键和值的功能:
1 2 3 4 5 6 7 8 9 10 11 12
| function mappedObject(obj, keyMapper, valueMapper) { const mapped = {}; const keys = Object.keys(obj); const mapKey = typeof keyMapper == 'function'; const mapVal = typeof valueMapper == 'function'; for (let i = 0; i < keys.length; i++) { const key = mapKey ? keyMapper(keys[i]) : keys[i]; const val = mapVal ? valueMapper(obj[keys[i]]) : obj[keys[i]]; mapped[key] = val; } return mapped; }
|