JS `for…of` 循环:遍历可迭代对象(数组、字符串、Map、Set)

各位观众老爷,今天咱们聊聊 JavaScript 里一个非常好用,又经常被忽略的循环方式:for...of。这玩意儿就像个瑞士军刀,简单实用,专治各种“遍历恐惧症”。

啥是可迭代对象?

在深入 for...of 之前,先得搞清楚一个概念:可迭代对象(Iterable)。这名字听起来高大上,其实说白了,就是可以用 for...of 循环来遍历的东西。常见的可迭代对象包括:

  • 数组 (Array): 这个不用多说,JavaScript 里最常用的数据结构之一。
  • 字符串 (String): 别忘了,字符串也可以看作是字符的数组。
  • Map: 一种键值对的集合,类似于对象,但键可以是任意类型。
  • Set: 一种值的集合,里面的值都是唯一的。
  • arguments 对象: 函数内部可以访问到的,包含了函数调用时传入的所有参数。
  • NodeList: 通过 document.querySelectorAll() 等方法获取的 DOM 元素集合。
  • 用户自定义的可迭代对象: 这个咱们后面会讲到,比较高级。

如果一个对象实现了 Symbol.iterator 方法,那它就是可迭代的。这个方法返回一个迭代器对象,迭代器对象有一个 next() 方法,每次调用 next() 方法都会返回一个包含 valuedone 属性的对象。value 是当前遍历到的值,done 是一个布尔值,表示是否遍历完成。

for...of 的基本语法

for...of 循环的语法非常简单:

for (const element of iterable) {
  // 在这里处理 element
}
  • element: 每次循环迭代时,会被赋值为可迭代对象中的下一个值。
  • iterable: 要遍历的可迭代对象。

for...of 的威力:各种可迭代对象的遍历

咱们来举几个例子,看看 for...of 在不同可迭代对象上的表现:

1. 数组 (Array)

const myArray = ['apple', 'banana', 'cherry'];

for (const fruit of myArray) {
  console.log(fruit); // 输出: apple, banana, cherry
}

是不是很简单? for...of 直接把数组里的每个元素都取出来了,避免了使用索引的麻烦。

2. 字符串 (String)

const myString = 'Hello';

for (const character of myString) {
  console.log(character); // 输出: H, e, l, l, o
}

字符串被分解成一个个字符,方便你对字符串进行逐个字符的处理。

3. Map

const myMap = new Map();
myMap.set('name', 'Alice');
myMap.set('age', 30);

for (const [key, value] of myMap) {
  console.log(`Key: ${key}, Value: ${value}`);
  // 输出: Key: name, Value: Alice
  //       Key: age, Value: 30
}

注意,遍历 Map 时,for...of 返回的是一个包含键和值的数组。我们可以使用解构赋值 [key, value] 来方便地获取键和值。

4. Set

const mySet = new Set();
mySet.add('apple');
mySet.add('banana');
mySet.add('cherry');

for (const fruit of mySet) {
  console.log(fruit); // 输出: apple, banana, cherry
}

Set 的遍历和数组类似,直接返回 Set 中的每个值。

5. arguments 对象

function myFunction() {
  for (const arg of arguments) {
    console.log(arg);
  }
}

myFunction(1, 2, 3); // 输出: 1, 2, 3

在函数内部,arguments 对象可以让你访问到所有传入的参数,即使你没有在函数定义中明确声明这些参数。

6. NodeList

const paragraphs = document.querySelectorAll('p');

for (const paragraph of paragraphs) {
  console.log(paragraph.textContent); // 输出所有段落的文本内容
}

这个例子需要在浏览器环境中运行。 document.querySelectorAll('p') 会返回一个包含所有 <p> 元素的 NodeList,然后你可以用 for...of 遍历这些元素。

for...of vs. for...in vs. forEach

JavaScript 里遍历数组或对象的方法有很多,for...of 只是其中一种。 咱们来比较一下 for...of 和另外两种常见的循环方式:for...inforEach

特性 for...of for...in forEach
遍历对象 可迭代对象 (数组, 字符串, Map, Set 等) 对象的 (包括原型链上的可枚举属性) 数组
返回值 元素的值 键 (字符串) undefined
break / continue 支持 支持 不支持 (只能使用 return 来模拟 continue)
可读性 较低 (容易出错) 中等
  • for...in 这个循环主要是用来遍历对象的,而不是数组。它会遍历对象的所有可枚举属性的,包括从原型链上继承来的属性。所以,用 for...in 遍历数组很容易出错,因为它会把数组的索引当作字符串来处理,而且还会遍历到数组的原型属性。

    const myArray = ['apple', 'banana', 'cherry'];
    
    for (const index in myArray) {
      console.log(index); // 输出: 0, 1, 2, (以及可能的原型属性)
    }
  • forEach 这是数组提供的一个方法,专门用来遍历数组。它接受一个回调函数作为参数,每次循环迭代时,都会把数组的元素、索引和数组本身传递给回调函数。

    const myArray = ['apple', 'banana', 'cherry'];
    
    myArray.forEach((fruit, index, array) => {
      console.log(`Index: ${index}, Fruit: ${fruit}`);
    });

    forEach 的一个缺点是,它不支持 breakcontinue 语句。如果你需要在循环中提前退出或跳过某些元素,forEach 就无能为力了。

总而言之,for...of 更加简洁明了,专门用来遍历可迭代对象的值,而 for...in 遍历对象的键,forEach 遍历数组,各有各的用途。

自定义可迭代对象

前面提到过,你可以创建自定义的可迭代对象。 这需要实现 Symbol.iterator 方法。 咱们来举个例子,创建一个可以生成斐波那契数列的迭代器:

class FibonacciSequence {
  constructor(max) {
    this.max = max;
  }

  [Symbol.iterator]() {
    let a = 0;
    let b = 1;
    let n = 0;
    const max = this.max;

    return {
      next() {
        if (n > max) {
          return { done: true };
        }

        const value = a;
        a = b;
        b = value + b;
        n++;

        return { value: value, done: false };
      }
    };
  }
}

const fibonacci = new FibonacciSequence(10);

for (const number of fibonacci) {
  console.log(number); // 输出斐波那契数列的前 11 项 (0 到 10)
}

这个例子稍微复杂一点,但是展示了如何使用 Symbol.iterator 来创建一个自定义的迭代器。

  • FibonacciSequence 类接受一个 max 参数,表示要生成的斐波那契数列的最大项数。
  • [Symbol.iterator]() 方法返回一个迭代器对象。
  • 迭代器对象的 next() 方法每次调用都会返回一个包含 valuedone 属性的对象。
  • n 大于 max 时,next() 方法返回 { done: true },表示迭代完成。

for...of 的一些高级用法

除了基本的遍历之外,for...of 还有一些高级用法,可以让你更加灵活地处理数据。

1. 解构赋值

在遍历 Map 或者包含多个属性的对象数组时,可以使用解构赋值来方便地获取属性值。

const users = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 },
  { name: 'Charlie', age: 35 }
];

for (const { name, age } of users) {
  console.log(`Name: ${name}, Age: ${age}`);
}

2. 与展开运算符结合

可以把 for...of 和展开运算符 (...) 结合起来,把可迭代对象转换成数组。

const myString = 'Hello';
const characters = [...myString]; // characters 现在是一个数组: ['H', 'e', 'l', 'l', 'o']

3. 遍历生成器函数

生成器函数是一种特殊的函数,可以使用 yield 关键字来暂停和恢复执行。 for...of 可以用来遍历生成器函数生成的值。

function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

for (const value of myGenerator()) {
  console.log(value); // 输出: 1, 2, 3
}

for...of 的注意事项

  • for...of 只能用于可迭代对象。如果尝试用 for...of 遍历一个不可迭代对象,会抛出一个错误。
  • for...of 不会遍历对象的属性,只会遍历可迭代对象的值。 如果需要遍历对象的属性,应该使用 for...in 或者 Object.keys() / Object.values() / Object.entries() 方法。
  • for...of 循环中可以使用 breakcontinue 语句来控制循环的流程。

总结

for...of 是 JavaScript 中一个强大而灵活的循环方式,它可以用来遍历各种可迭代对象。 相比于 for...inforEachfor...of 更加简洁明了,易于理解,并且支持 breakcontinue 语句。 掌握 for...of 可以让你写出更加优雅和高效的代码。

好了,今天的 for...of 讲座就到这里。 希望大家以后在写代码的时候,能多多使用 for...of,告别“遍历恐惧症”,拥抱简洁高效的编程生活! 下次再见!

发表回复

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