JavaScript 的 map() 如何使用,与 Map 有什么区别?
先明确两者的作用
Array.prototype.map() 对数组中的元素逐一执行转换,把结果组成一个新数组。它适合已有一组数据、需要得到另一组对应数据的场景。
new Map() 创建一个保存键值关系的数据结构,适合保存、查询或更新某个键对应的值。
JavaScript 区分大小写,数组方法 map() 和构造函数 Map 是两个独立的 API。两者都涉及对应关系,使用方式和目的不同。
数组 map() 到底做了什么?
例如,把数组中的每个数乘以 2:
const numbers = [1, 2, 3];
const doubled = numbers.map(number => number * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3]
这里的箭头函数是传给 map() 的回调函数。map() 依次把这个数组中的元素传给它:
- 输入
1,返回2。 - 输入
2,返回4。 - 输入
3,返回6。
这些返回值组成新数组 [2, 4, 6],与原数组中的元素在位置和顺序上对应。
这个数组没有空槽,下面的 for 循环也能完成同样的转换:
const numbers = [1, 2, 3];
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
用 map() 可以在一行中表达遍历、转换并把结果收集到新数组的过程。
回调函数可以接收什么?
回调函数可以接收三个参数:当前元素、当前索引和原数组。
const names = ["Alice", "Bob"];
const labels = names.map((name, index) => {
return `${index + 1}. ${name}`;
});
console.log(labels); // ["1. Alice", "2. Bob"]
用不到索引和原数组时,只写第一个参数即可。回调的返回值会放在新数组的对应位置。
实际开发中什么时候使用 map()?
从对象数组中提取字段
假设有一组用户数据,需要从每个用户对象中取出对应的用户名:
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const names = users.map(user => user.name);
console.log(names); // ["Alice", "Bob"]
把数据转换成展示所需的结构
例如,将每个商品对象转换成页面展示所需的对象:
const products = [
{ id: 1, name: "键盘", price: 199 },
{ id: 2, name: "鼠标", price: 89 }
];
const items = products.map(product => ({
id: product.id,
label: `${product.name}:¥${product.price}`
}));
console.log(items);
// [
// { id: 1, label: "键盘:¥199" },
// { id: 2, label: "鼠标:¥89" }
// ]
箭头函数中的 ({ ... }) 表示直接返回一个对象字面量。
map() 和 for 循环应该怎么选?
如果需要将每个元素的转换结果组成新数组,通常可以用 map()。其他需求可以选择相应的方法或循环:
| 需求 | 常用选择 |
|---|---|
| 把每个元素转换成一个结果 | map() |
| 只保留满足条件的元素 | filter() |
| 找到第一个符合条件的元素 | find() |
| 逐项执行操作,不需要收集返回值 | forEach() 或循环 |
需要使用 break、continue 控制过程 | for 或 for...of |
例如,要在找到第一个负数后停止遍历,可以使用循环:
const numbers = [3, 5, -2, 8];
for (const number of numbers) {
if (number < 0) {
console.log(number); // -2
break;
}
}
在 map() 的回调中使用 return,只会返回当前元素的转换结果,遍历不会因此终止。
选择 map() 的主要理由是它能清楚地表达数据转换的意图。它不一定比 for 更快,具体性能取决于运行环境和处理逻辑。
new Map() 有什么用途?
Map 保存键与值之间的对应关系。例如,用用户 ID 查询用户名:
const userNames = new Map();
userNames.set(101, "Alice");
userNames.set(102, "Bob");
console.log(userNames.get(101)); // "Alice"
console.log(userNames.has(102)); // true
console.log(userNames.size); // 2
常用操作包括:
set(key, value):添加键值对,已有相同键时更新值。get(key):取得该键对应的值。has(key):检查键是否存在。delete(key):删除键值对。size:取得键值对数量。
键可以是字符串、数字,也可以是对象。对象作为键时,按对象身份区分;两个属性相同但分别创建的对象仍然是不同的键。
const user = { id: 101 };
const permissions = new Map();
permissions.set(user, "editor");
console.log(permissions.get(user)); // "editor"
console.log(permissions.get({ id: 101 })); // undefined
需要按 ID 查数据、缓存某个对象的计算结果,或维护一组可以增删的键值关系时,都可以考虑 Map。
为什么它们都使用 map 这个词?
“映射”表达的是一种对应关系。
数组的 map() 使用转换函数,为每个输入元素计算对应的输出。例如:
1 → 2
2 → 4
3 → 6
Map 保存键到值的对应关系:
101 → "Alice"
102 → "Bob"
数组 map() 中,不同输入可以得到相同输出,例如 [1, 3].map(() => 0) 得到 [0, 0]。Map 中,一个键只能对应一个当前值,但不同键可以对应相同的值。
两者可以配合使用:先用 map() 将用户转换成 [key, value] 数组,再构造用于查询的 Map:
const users = [
{ id: 101, name: "Alice" },
{ id: 102, name: "Bob" }
];
const usersById = new Map(
users.map(user => [user.id, user])
);
console.log(usersById.get(102).name); // "Bob"
如果用户 ID 重复,后面的值会覆盖前面的值。
使用 map() 时容易忽略什么?
使用花括号时要显式 return
const numbers = [1, 2, 3];
const wrong = numbers.map(number => {
number * 2;
});
console.log(wrong); // [undefined, undefined, undefined]
const correct = numbers.map(number => {
return number * 2;
});
没有 return 时,回调返回 undefined,结果数组的对应位置也会是 undefined。
新数组不代表深拷贝
map() 自身不会修改原数组,但回调函数里的代码仍然可以修改原对象:
const users = [{ name: "Alice", active: false }];
const result = users.map(user => {
user.active = true;
return user;
});
console.log(users[0].active); // true
console.log(result === users); // false
console.log(result[0] === users[0]); // true
如果希望保留原对象,可以返回一个新对象:
const result = users.map(user => ({
...user,
active: true
}));
对象展开只复制一层,嵌套对象仍可能共享引用。稀疏数组中的空槽不会触发 map() 的回调,结果数组也会保留对应的空槽。
备注:内容仅供参考。
