嘿,各位编程界的弄潮儿们,今天咱们来聊聊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
有了更深入的了解。记住,选择合适的工具,才能写出更优雅、更高效的代码。下次再见!