for...of 循环遍历 Set 和 Map 时的注意事项
一、遍历 Set 集合的注意事项
Set 是 JavaScript 中用于存储唯一值的有序集合,使用 for...of 遍历时需注意以下特性:
1. 遍历顺序与插入顺序一致
Set 集合遵循“插入顺序”,遍历时会按照元素添加的顺序依次输出。
const set = new Set([3, 1, 2]);
for (const item of set) {
console.log(item); // 输出:3, 1, 2(与插入顺序一致)
}
2. 直接获取元素值,无需额外处理
Set 的每个元素本身就是值,for...of 可直接遍历元素,无需像数组一样通过索引获取。
const fruits = new Set(['apple', 'banana', 'orange']);
for (const fruit of fruits) {
console.log(fruit); // 直接输出元素值
}
3. 无法通过索引操作元素
Set 没有索引概念,遍历时无法通过下标访问或修改元素(需通过 delete
或 clear
方法操作)。
const set = new Set([1, 2, 3]);
for (const item of set) {
// 错误示例:无法通过索引修改
// set[0] = 10; // 报错,Set 无索引属性
if (item === 2) set.delete(item); // 正确方式:通过值删除
}
4. 与 forEach 的行为对比
- for...of:可使用
break
/continue
中断循环,支持async/await
。 - forEach:无法中断循环,需通过抛出异常终止。
```javascript
const set = new Set([1, 2, 3, 4]);
// for...of 可提前终止
for (const item of set) {
if (item === 3) break;
console.log(item); // 输出:1, 2
}
// forEach 无法直接终止,需抛出异常
set.forEach(item => {
if (item === 3) throw new Error('终止循环');
console.log(item);
});
##### 5. **遍历 Symbol 类型元素**
Set 支持存储 Symbol 类型,for...of 可正常遍历(需注意 Symbol 的唯一性)。
```javascript
const sym1 = Symbol('a');
const sym2 = Symbol('b');
const set = new Set([sym1, sym2]);
for (const sym of set) {
console.log(sym); // 输出 Symbol(a), Symbol(b)
}
二、遍历 Map 集合的注意事项
Map 是键值对的有序集合,每个元素为 [key, value]
数组,for...of 遍历时需特殊处理键值对。
1. 直接获取键值对数组
for...of 遍历 Map 时,每个迭代项是包含 [key, value]
的数组,需解构赋值获取键值。
const map = new Map([
['name', 'John'],
['age', 30],
['city', 'New York']
]);
// 解构获取键值
for (const [key, value] of map) {
console.log(`${
key}: ${
value}`);
// 输出:name: John, age: 30, city: New York
}
2. 遍历顺序与插入顺序一致
Map 同样遵循插入顺序,遍历时键值对的顺序与添加时一致。
const map = new Map();
map.set(2, 'two');
map.set(1, 'one');
map.set(3, 'three');
for (const [key, val] of map) {
console.log(key); // 输出:2, 1, 3(插入顺序)
}
3. 结合 keys()/values()/entries() 方法
Map 提供三种遍历方法,需配合 for...of 使用:
- map.keys():遍历所有键。
- map.values():遍历所有值。
- map.entries():遍历所有键值对(默认行为)。
```javascript
// 遍历键
for (const key of map.keys()) {
console.log(key); // 2, 1, 3
}
// 遍历值
for (const val of map.values()) {
console.log(val); // two, one, three
}
// 等价于直接遍历 map
for (const item of map.entries()) {
console.log(item); // [2, 'two'], [1, 'one'], [3, 'three']
}
##### 4. **键的类型支持**
Map 的键可以是任意类型(包括对象、函数、Symbol 等),for...of 遍历时需注意:
```javascript
const obj = { id: 1 };
const map = new Map();
map.set(obj, 'object-key');
map.set(Symbol('sym'), 'symbol-key');
for (const [key, val] of map) {
console.log(`${typeof key}: ${val}`);
// 输出:object: object-key, symbol: symbol-key
}
5. 与 forEach 的区别
- for...of:可通过解构获取键值,支持中断循环。
- forEach:通过回调函数接收
(value, key, map)
三个参数,无法中断。map.forEach((value, key, map) => { console.log(`${ key}: ${ value}`); // 无法使用 break/continue,需通过逻辑控制是否继续 });
三、通用注意事项(Set/Map 共通)
遍历期间修改集合的影响
- 在 for...of 循环中添加或删除元素可能导致 不可预测的结果(不同浏览器实现可能不同),建议避免。
const set = new Set([1, 2, 3]); for (const item of set) { if (item === 2) set.add(4); // 不建议在遍历中修改集合 }
- 在 for...of 循环中添加或删除元素可能导致 不可预测的结果(不同浏览器实现可能不同),建议避免。
与迭代器的兼容性
- Set 和 Map 都实现了
Iterator
接口,for...of 本质上是对迭代器的封装,可配合next()
方法手动控制遍历。const set = new Set([1, 2, 3]); const iterator = set[Symbol.iterator](); console.log(iterator.next()); // { value: 1, done: false }
- Set 和 Map 都实现了
空集合的处理
- 当 Set 或 Map 为空时,for...of 循环不会执行任何代码块,无需额外判断
size === 0
。
- 当 Set 或 Map 为空时,for...of 循环不会执行任何代码块,无需额外判断
四、最佳实践建议
Set 遍历场景
- 当需要按插入顺序遍历唯一值时,直接使用 for...of。
- 若需过滤元素,可结合
Array.from(set).filter()
转换为数组处理。
Map 遍历场景
- 使用解构赋值
[key, value]
同时获取键值对。 - 若仅需键或值,优先使用
keys()
或values()
提升可读性。
- 使用解构赋值
避免遍历中修改集合
- 如需修改,建议先将集合转换为数组(如
[...set]
),再遍历处理。
- 如需修改,建议先将集合转换为数组(如
通过理解 for...of 在 Set 和 Map 中的遍历特性,可更高效地处理集合数据,同时避免因特性不熟悉导致的逻辑错误。