JavaScript Map forEach() 方法:遍历 Map 的正确姿势
写代码的时候,我们经常需要把 Map 里存的东西一个个拿出来看看、处理一下。forEach() 方法就是专门干这个活的——它让你能对 Map 里的每一组键值对执行一段代码。
这个方法看起来简单,但用好了能让代码更清晰,用不好也可能踩坑。今天就来聊聊怎么用好它。
语法与工作机制
map对象.forEach(回调函数[, thisArg]);
参数说明:
-
回调函数:Map 里的每一组键值对都会调用一次这个函数。它接收三个参数:
-
value:当前遍历到的值 -
key:当前遍历到的键 -
map:正在被遍历的 Map 对象本身(通常用不上)
-
-
thisArg(可选):执行回调函数时,
this关键字指向的对象。如果不传,在严格模式下就是undefined,非严格模式下是全局对象。
返回值: undefined。forEach() 不返回任何东西,它只管遍历,不管结果。
代码示例:从入门到实战
示例1:基础遍历——只取值
有时候我们只关心值,键暂时用不上。
// 代码号:学习编程,用 forEach 遍历 Map 的值
let frameworks = new Map();
frameworks.set(1, "React");
frameworks.set(2, "Vue");
frameworks.set(3, "Svelte");
// 定义一个简单的处理函数
function showFramework(name) {
console.log(name);
}
frameworks.forEach(showFramework);
// 输出:
// React
// Vue
// Svelte
个人见解:很多人刚接触时以为回调函数只能传一个参数,其实 forEach 会把值作为第一个参数传进去。所以哪怕你只定义了一个参数,也能拿到值。但要注意,如果回调函数里你需要用到键,那就得把第二个参数也写上。
示例2:同时获取键和值
项目开发中,我们经常需要同时拿到键和值来处理。
// 代码号:学习编程,遍历时同时获取键和值
let userRoles = new Map();
userRoles.set("zhang_san", "管理员");
userRoles.set("li_si", "编辑");
userRoles.set("wang_wu", "访客");
function showUserInfo(role, username) {
console.log(`用户 ${username} 的角色是:${role}`);
}
userRoles.forEach(showUserInfo);
// 输出:
// 用户 zhang_san 的角色是:管理员
// 用户 li_si 的角色是:编辑
// 用户 wang_wu 的角色是:访客
深度解析:注意这里参数的顺序——第一个是值,第二个是键。这个顺序可能会让你觉得有点反直觉,但 JavaScript 就是这么设计的。所以在写回调函数时,记得先定义值的参数,再定义键的参数。
示例3:使用 thisArg 绑定上下文
thisArg 参数在某些场景下很实用,比如你想在回调函数里访问外部的某个对象。
// 代码号:学习编程,用 thisArg 绑定上下文
let scores = new Map();
scores.set("张三", 85);
scores.set("李四", 92);
scores.set("王五", 78);
let counter = {
passCount: 0,
// 这里用普通函数,因为箭头函数不能绑定 this
checkPass: function(score, name) {
if (score >= 60) {
this.passCount++;
console.log(`${name} 及格了,分数:${score}`);
}
}
};
scores.forEach(counter.checkPass, counter);
console.log(`及格人数:${counter.passCount}`);
// 输出:
// 张三 及格了,分数:85
// 李四 及格了,分数:92
// 王五 及格了,分数:78
// 及格人数:3
个人建议:如果你用的是箭头函数作为回调,thisArg 是无效的,因为箭头函数不绑定自己的 this。所以在需要绑定上下文的场景,记得用普通函数。
示例4:遍历过程中修改 Map
这是个需要注意的地方——在 forEach 遍历过程中直接修改 Map,可能会产生预期外的结果。
// 代码号:学习编程,遍历时修改 Map 的行为
let items = new Map();
items.set("a", 1);
items.set("b", 2);
items.set("c", 3);
// 尝试在遍历时删除正在遍历的项
items.forEach((value, key, map) => {
console.log(`处理: ${key}=${value}`);
if (key === "b") {
map.delete("a"); // 删除还没遍历到的 a
}
});
// 输出:
// 处理: a=1
// 处理: b=2
// 处理: c=3
深度解析:这个例子有点意思。删除键 a 的时候,a 其实还没被遍历到?不对,上面的输出显示 a 已经被处理了。实际上,forEach 在开始遍历时会记录 Map 中的条目,删除已经遍历过的条目不影响后续,但如果删除的是还没遍历到的条目,不同的 JavaScript 引擎行为可能不一致。稳妥的做法是:不要在遍历时直接修改原 Map,如果需要过滤或删除,先收集要操作的键,遍历完再统一处理。
本节课程知识要点
-
参数顺序要记牢:回调函数的参数依次是(值, 键, Map本身)。值在前,键在后,这个顺序不能搞反。
-
thisArg的用法:想改变回调函数内部的this指向,就传第二个参数。但箭头函数不适用。 -
没有返回值:
forEach()返回undefined,它只负责遍历,不产出新数据。如果你想基于原 Map 生成新数据,可以考虑用for...of循环配合数组方法。 -
遍历时的修改:尽量避免在
forEach回调里直接修改原 Map(添加、删除、清空),可能会导致不可预测的结果。可以先收集要删除的键,遍历结束后再处理。
常见问题解答
Q1: forEach() 和 for...of 遍历 Map 有什么区别?
-
forEach()是 Map 对象自带的遍历方法,语法更简洁,直接拿到值和键。 -
for...of遍历 Map 会返回一个[key, value]数组,需要用解构来拿到键和值。 -
如果你需要在遍历中提前终止(比如用
break跳出循环),只能用for...of,因为forEach没法中途停止。
Q2: 回调函数里能异步操作吗?
可以,但要注意 forEach 不会等待异步操作完成。如果你需要按顺序处理异步任务,用 for...of 配合 async/await 会更合适。
Q3: 空 Map 调用 forEach() 会怎样?
不会报错,回调函数一次都不会执行,程序正常继续运行。
Q4: forEach() 遍历的顺序有保证吗?
Map 对象会按照插入的顺序来遍历键值对,这个顺序是有保证的。所以你可以放心地依赖插入顺序。