嘿,各位编程界的弄潮儿们,今天咱们来聊聊JavaScript里 for...of 循环和 Map、Set 这俩哥们儿之间的那些事儿。这可不是简单的语法糖,而是能让你的代码更优雅、更高效的利器。准备好了吗?咱们这就开始!
开场白:迭代器,神秘的幕后推手
在深入 for...of、Map 和 Set 之前,咱们先得认识一位幕后英雄:迭代器。你可以把迭代器想象成一个“指针”,它能帮你一个一个地访问集合里的元素,而不用关心集合内部是怎么存储的。
JavaScript 里,一个对象如果想支持迭代,就必须提供一个 Symbol.iterator 方法。这个方法会返回一个迭代器对象,这个迭代器对象必须包含一个 next() 方法。每次调用 next(),它都会返回一个包含 value 和 done 属性的对象。value 是当前元素的值,done 是一个布尔值,表示迭代是否结束。
听起来有点抽象?没关系,咱们先记住这个概念,后面会结合 Map 和 Set 来具体讲解。
for...of:优雅的遍历利器
for...of 循环是 ES6 引入的,它专门用来遍历可迭代对象。相比传统的 for 循环和 forEach 方法,for...of 循环更加简洁、易读,而且还能处理 break、continue 和 return 语句。
语法非常简单:
for (const element of iterable) {
// 对 element 做一些操作
}
这里的 iterable 就是一个可迭代对象,比如数组、字符串、Map、Set 等等。每次循环,element 都会被赋值为 iterable 中的下一个元素。
Map:键值对的乐园
Map 对象是一种存储键值对的数据结构,和普通对象不同的是,Map 的键可以是任意类型,而不仅仅是字符串。这让 Map 在很多场景下都比普通对象更加灵活。
创建一个 Map:
const myMap = new Map();
// 添加键值对
myMap.set('name', 'Alice');
myMap.set(123, 'Number Key');
myMap.set({}, 'Object Key');
// 获取值
console.log(myMap.get('name')); // 输出: Alice
console.log(myMap.get(123)); // 输出: Number Key
console.log(myMap.get({})); // 输出: undefined (因为是不同的对象)
注意最后一个 get({}),虽然键看起来一样,但实际上是不同的对象,所以 Map 找不到对应的键。
Set:独一无二的元素集合
Set 对象是一种存储唯一值的集合。也就是说,Set 里的元素不会重复。这在需要去重的场景下非常有用。
创建一个 Set:
const mySet = new Set();
// 添加元素
mySet.add(1);
mySet.add(2);
mySet.add(1); // 添加重复元素,Set 会自动去重
// 检查元素是否存在
console.log(mySet.has(1)); // 输出: true
console.log(mySet.has(3)); // 输出: false
for...of 遍历 Map:深入键值对的世界
现在,咱们来看看 for...of 循环如何遍历 Map。由于 Map 存储的是键值对,所以 for...of 循环每次迭代都会返回一个包含键和值的数组 [key, value]。
const myMap = new Map();
myMap.set('name', 'Alice');
myMap.set('age', 30);
myMap.set('city', 'New York');
for (const [key, value] of myMap) {
console.log(`Key: ${key}, Value: ${value}`);
}
// 输出:
// Key: name, Value: Alice
// Key: age, Value: 30
// Key: city, Value: New York
是不是很简洁?for...of 循环自动帮你解构了 [key, value] 数组,让你直接访问键和值。
你也可以使用 Map 提供的 keys()、values() 和 entries() 方法来获取迭代器,然后用 for...of 循环遍历。
keys():返回一个包含Map中所有键的迭代器。values():返回一个包含Map中所有值的迭代器。entries():返回一个包含Map中所有键值对的迭代器(和直接遍历Map的效果一样)。
const myMap = new Map();
myMap.set('name', 'Alice');
myMap.set('age', 30);
myMap.set('city', 'New York');
// 遍历键
for (const key of myMap.keys()) {
console.log(`Key: ${key}`);
}
// 遍历值
for (const value of myMap.values()) {
console.log(`Value: ${value}`);
}
// 遍历键值对
for (const [key, value] of myMap.entries()) {
console.log(`Key: ${key}, Value: ${value}`);
}
for...of 遍历 Set:探索唯一值的海洋
for...of 循环遍历 Set 就更简单了,因为 Set 存储的是唯一值,所以每次迭代都会返回一个值。
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(3);
for (const value of mySet) {
console.log(`Value: ${value}`);
}
// 输出:
// Value: 1
// Value: 2
// Value: 3
Set 也提供了一个 values() 方法,返回一个包含 Set 中所有值的迭代器。不过,由于 Set 的键和值是相同的,所以 keys() 方法也返回一个包含 Set 中所有值的迭代器。entries()方法返回包含[value, value] 的数组。
const mySet = new Set();
mySet.add(1);
mySet.add(2);
mySet.add(3);
// 遍历值 (使用 values() 方法)
for (const value of mySet.values()) {
console.log(`Value: ${value}`);
}
// 遍历值 (使用 keys() 方法)
for (const value of mySet.keys()) { // 注意这里仍然输出的是值,而不是键
console.log(`Value: ${value}`);
}
// 遍历条目 (使用 entries() 方法)
for (const [key, value] of mySet.entries()) {
console.log(`Key: ${key}, Value: ${value}`); // key 和 value 都是相同的值
}
Map 和 Set 的一些实用技巧
-
Map的forEach()方法:Map对象还提供了一个forEach()方法,可以用来遍历Map。const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); myMap.set('city', 'New York'); myMap.forEach((value, key) => { console.log(`Key: ${key}, Value: ${value}`); }); -
Set的forEach()方法:Set对象也提供了一个forEach()方法,可以用来遍历Set。const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.add(3); mySet.forEach(value => { console.log(`Value: ${value}`); }); -
Map和Set的大小: 你可以使用size属性来获取Map和Set的大小(即包含的元素个数)。const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.add(3); console.log(myMap.size); // 输出: 2 console.log(mySet.size); // 输出: 3 -
Map和Set的转换: 你可以使用扩展运算符 (...) 将Map和Set转换为数组。const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.add(3); const mapArray = [...myMap]; // [[ 'name', 'Alice' ], [ 'age', 30 ]] const setArray = [...mySet]; // [ 1, 2, 3 ] console.log(mapArray); console.log(setArray); -
使用
Array.from()转换: 也可以使用Array.from()方法将Map和Set转换为数组。const myMap = new Map(); myMap.set('name', 'Alice'); myMap.set('age', 30); const mySet = new Set(); mySet.add(1); mySet.add(2); mySet.add(3); const mapArray = Array.from(myMap); // [[ 'name', 'Alice' ], [ 'age', 30 ]] const setArray = Array.from(mySet); // [ 1, 2, 3 ] console.log(mapArray); console.log(setArray);
性能考量
| 操作 | Map | Object | Set | Array |
|---|---|---|---|---|
| 添加元素 | O(1) | O(1) | O(1) | O(1) |
| 删除元素 | O(1) | O(1) | O(1) | O(n) |
| 查找元素 (key) | O(1) | O(1) | O(n) | O(n) |
| 检查元素存在 | O(1) | O(1) | O(1) | O(n) |
| 顺序 | 保持顺序 | 不保证顺序 | 保持顺序 | 保持顺序 |
- 插入和删除:
Map和Set通常在插入和删除操作上比普通对象和数组更有效率,特别是当数据量很大时。 - 查找:
Map提供了比普通对象更快的键查找速度(平均情况下)。Set在检查元素是否存在时也比数组更有效率。 - 顺序:
Map和Set都会保持元素的插入顺序,这在某些场景下非常重要。普通对象的键的顺序是不确定的。
总结:选择合适的工具
for...of 循环是遍历 Map 和 Set 的强大工具,它简洁、易读,而且还能处理 break、continue 和 return 语句。Map 和 Set 提供了更高效的数据存储和操作方式,特别是在需要键值对存储、唯一值集合或者需要保持元素顺序的场景下。
选择使用哪个数据结构,取决于你的具体需求。如果你需要存储键值对,并且键可以是任意类型,那么 Map 是一个不错的选择。如果你需要存储唯一值的集合,那么 Set 是一个不错的选择。如果你需要遍历集合,那么 for...of 循环是一个不错的选择。
更进一步:自定义迭代器
还记得咱们一开始提到的迭代器吗?实际上,你可以为任何对象定义自己的迭代器,让它支持 for...of 循环。
const myObject = {
data: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.data.length) {
return { value: this.data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (const value of myObject) {
console.log(value);
}
// 输出:
// 1
// 2
// 3
// 4
// 5
在这个例子中,我们为 myObject 定义了一个 Symbol.iterator 方法,它返回一个迭代器对象。这个迭代器对象包含一个 next() 方法,每次调用 next(),它都会返回 data 数组中的下一个元素。
自定义迭代器可以让你控制对象如何被遍历,这在一些高级场景下非常有用。
好了,今天的讲座就到这里。希望大家对 for...of 循环、Map 和 Set 有了更深入的了解。记住,选择合适的工具,才能写出更优雅、更高效的代码。下次再见!