对象的映射函数(而非数组)

对象的映射函数(而非数组)

技术背景

在 JavaScript 中,Array 原型上有 map 方法,但 Object 对象本身没有原生的 map 方法。不过在实际开发中,我们可能需要对对象的键值对进行映射操作,将对象的每个值进行转换,生成一个新的对象。本文将介绍多种实现对象映射功能的方法。

实现步骤

直接修改原对象

可以使用 Object.keysforEach 或者 for...in 循环来遍历对象并修改其值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 使用 Object.keys 和 forEach
var myObject = { 'a': 1, 'b': 2, 'c': 3 };
Object.keys(myObject).forEach(function(key, index) {
myObject[key] *= 2;
});
console.log(myObject); // => { 'a': 2, 'b': 4, 'c': 6 }

// 使用 for...in
var myObject = { 'a': 1, 'b': 2, 'c': 3 };
for (var key in myObject) {
if (myObject.hasOwnProperty(key)) {
myObject[key] *= 2;
}
}
console.log(myObject); // { 'a': 2, 'b': 4, 'c': 6 }

返回新对象

以下几种方法可以在不修改原对象的情况下返回一个新对象:

使用 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); // => { 'a': 2, 'b': 4, 'c': 6 }
console.log(myObject); // => { 'a': 1, 'b': 2, 'c': 3 }

使用 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.keysObject.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); // 打印 'a', 'b', 'c'
}

核心代码

以下是一个通用的对象映射函数:

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.keysforEachfor...in 循环。
  • 当需要返回新对象且不修改原对象时,推荐使用 Object.fromEntriesObject.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;
}

对象的映射函数(而非数组)
https://119291.xyz/posts/map-function-for-objects-instead-of-arrays/
作者
ww
发布于
2025年5月26日
许可协议