在 JavaScript 中,迭代器(Iterator)是遍历集合元素的一个重要工具,它为我们提供了一种统一的方式来访问集合(如数组、对象、Map、Set 等)中的每个元素。通过迭代器,我们不需要了解数据结构的具体实现,就能够高效地遍历其元素。

1. 迭代器基本概念

迭代器 是一个具有 next() 方法的对象。每次调用 next() 时,迭代器会返回一个对象,包含两个属性:

  • value:当前迭代元素的值。
  • done:一个布尔值,表示迭代是否完成。done: true 表示迭代已结束,done: false 表示还有更多元素。
const iterable = {
  current: 0,
  last: 3,
  next() {
    if (this.current <= this.last) {
      return { value: this.current++, done: false };
    } else {
      return { value: undefined, done: true };
    }
  }
};
 
console.log(iterable.next()); // { value: 0, done: false }
console.log(iterable.next()); // { value: 1, done: false }
console.log(iterable.next()); // { value: 2, done: false }
console.log(iterable.next()); // { value: 3, done: false }
console.log(iterable.next()); // { value: undefined, done: true }

在上述代码中,next() 方法控制了迭代过程,直到达到最后的元素。


2. 使用 for...of 遍历迭代器

for...of 循环是遍历迭代器的常见方式。所有实现了迭代器协议的对象(如数组、字符串、Map、Set 等)都可以直接用于 for...of 循环。

const arr = [1, 2, 3];
 
for (const value of arr) {
  console.log(value); // 输出 1, 2, 3
}

for...of 循环中,JavaScript 会自动调用对象的 Symbol.iterator 方法,并逐一调用 next() 返回每个元素。


3. 内建的可迭代对象

JavaScript 中有许多内建的可迭代对象,它们自动实现了迭代器接口。常见的可迭代对象有:

  • 数组:数组是一个典型的可迭代对象,可以直接在 for...of 中使用。
  • 字符串:字符串也是可迭代对象,可以逐个字符进行遍历。
  • Set 和 Map:这两种数据结构也是可迭代对象,Set 存储唯一的值,而 Map 存储键值对。
const set = new Set([1, 2, 3]);
for (const value of set) {
  console.log(value); // 输出 1, 2, 3
}
 
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
  console.log(key, value); // 输出 'a' 1, 'b' 2
}

4. 实现自定义可迭代对象

你可以通过实现 Symbol.iterator 方法,让一个自定义对象变得可迭代。这个方法需要返回一个迭代器对象,迭代器对象必须实现 next() 方法。

const iterable = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};
 
for (const value of iterable) {
  console.log(value); // 输出 1, 2, 3
}

在这个例子中,我们通过实现 Symbol.iterator 方法,允许 iterable 对象被 for...of 循环遍历。


5. 生成器(Generators)与迭代器

生成器(Generator)是一个特殊的函数,它可以暂停执行并在以后恢复。生成器返回的是一个迭代器对象,具有 next() 方法。生成器函数通过 function* 关键字声明,并通过 yield 表达式返回值。

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}
 
const gen = generator();
 
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

在这个例子中,generator 是一个生成器函数,每次调用 next() 都会返回一个值,直到完成所有 yield 的操作。


6. JavaScript 中的迭代器底层实现

JavaScript 中的迭代器原理本质上是基于对象的 next() 方法,每次调用时返回当前元素,并且会记录迭代的状态。浏览器和引擎通过管理这些状态来控制集合的迭代。

底层实现原理概述

  1. 生成器和 next()
    • 当我们调用生成器函数时,执行到 yield 时会暂停并返回一个值,同时保存当前执行的上下文。调用 next() 后,生成器函数会恢复执行并从上次暂停的地方继续,直到遇到下一个 yield 或结束。
  2. Symbol.iterator 和可迭代对象
    • 当我们遍历一个对象时,JavaScript 会自动调用 Symbol.iterator 方法,返回一个包含 next() 方法的迭代器对象。然后,for...of 会依次调用 next() 来获取下一个元素。
  3. 内存管理
    • 迭代器通常使用懒加载方式,直到调用 next() 时才会计算下一个值,这使得它们在处理大量数据时非常高效。

7. 总结

JavaScript 中的 迭代器(Iterator) 是一个非常强大的工具,它提供了一种统一的方式来遍历各种数据结构。无论是内建的可迭代对象,还是自定义的可迭代对象,都可以通过迭代器来进行高效的遍历操作。

  • 迭代器协议 定义了 next() 方法和 done 属性,它是实现遍历的基础。
  • for...of 循环简化了迭代器的使用,让我们能够轻松遍历各种可迭代对象。
  • 通过 生成器Symbol.iterator,我们可以创建自定义的可迭代对象,并灵活控制迭代过程。

理解迭代器的底层原理和实现方式,将有助于我们更高效地使用 JavaScript,并在处理大数据、流式数据等场景时,能够利用迭代器带来的性能优势。