JS `for…of` 循环遍历 `Map` 和 `Set` 的迭代器

嘿,各位编程界的弄潮儿们,今天咱们来聊聊JavaScript里 for...of 循环和 MapSet 这俩哥们儿之间的那些事儿。这可不是简单的语法糖,而是能让你的代码更优雅、更高效的利器。准备好了吗?咱们这就开始!

开场白:迭代器,神秘的幕后推手

在深入 for...ofMapSet 之前,咱们先得认识一位幕后英雄:迭代器。你可以把迭代器想象成一个“指针”,它能帮你一个一个地访问集合里的元素,而不用关心集合内部是怎么存储的。

JavaScript 里,一个对象如果想支持迭代,就必须提供一个 Symbol.iterator 方法。这个方法会返回一个迭代器对象,这个迭代器对象必须包含一个 next() 方法。每次调用 next(),它都会返回一个包含 valuedone 属性的对象。value 是当前元素的值,done 是一个布尔值,表示迭代是否结束。

听起来有点抽象?没关系,咱们先记住这个概念,后面会结合 MapSet 来具体讲解。

for...of:优雅的遍历利器

for...of 循环是 ES6 引入的,它专门用来遍历可迭代对象。相比传统的 for 循环和 forEach 方法,for...of 循环更加简洁、易读,而且还能处理 breakcontinuereturn 语句。

语法非常简单:

for (const element of iterable) {
  // 对 element 做一些操作
}

这里的 iterable 就是一个可迭代对象,比如数组、字符串、MapSet 等等。每次循环,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 都是相同的值
}

MapSet 的一些实用技巧

  • MapforEach() 方法: 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}`);
    });
  • SetforEach() 方法: Set 对象也提供了一个 forEach() 方法,可以用来遍历 Set

    const mySet = new Set();
    mySet.add(1);
    mySet.add(2);
    mySet.add(3);
    
    mySet.forEach(value => {
      console.log(`Value: ${value}`);
    });
  • MapSet 的大小: 你可以使用 size 属性来获取 MapSet 的大小(即包含的元素个数)。

    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
  • MapSet 的转换: 你可以使用扩展运算符 (...) 将 MapSet 转换为数组。

    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() 方法将 MapSet 转换为数组。

    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)
顺序 保持顺序 不保证顺序 保持顺序 保持顺序
  • 插入和删除: MapSet 通常在插入和删除操作上比普通对象和数组更有效率,特别是当数据量很大时。
  • 查找: Map 提供了比普通对象更快的键查找速度(平均情况下)。Set 在检查元素是否存在时也比数组更有效率。
  • 顺序: MapSet 都会保持元素的插入顺序,这在某些场景下非常重要。普通对象的键的顺序是不确定的。

总结:选择合适的工具

for...of 循环是遍历 MapSet 的强大工具,它简洁、易读,而且还能处理 breakcontinuereturn 语句。MapSet 提供了更高效的数据存储和操作方式,特别是在需要键值对存储、唯一值集合或者需要保持元素顺序的场景下。

选择使用哪个数据结构,取决于你的具体需求。如果你需要存储键值对,并且键可以是任意类型,那么 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 循环、MapSet 有了更深入的了解。记住,选择合适的工具,才能写出更优雅、更高效的代码。下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注