JavaScript 中 Iterable,Iterator,Generator 三者的区别与联系

3,092 阅读3分钟

在 JavaScript 中,我们经常会看到可迭代对象,迭代器和生成器的概念,它们之间有什么区别和联系呢,我就是想通过这篇文章总结一下,把这个问题搞明白。

Interable(可迭代对象)

Iterable 是指有 [Symbol.iterator] 属性的对象,这个属性 obj[Symbol.iterator] 就是 Iterator(迭代器)。也可以说可迭代对象是实现了 Symbol.iterator 方法的对象。

可迭代对象可以被 for..of 循环遍历,我们最常进行迭代操作的可迭代对象就是 Array,其实还有其他可迭代对象,例如 String、Set、Map、函数的 arguments 对象和 NodeList 对象等,这些对象都有默认的 Symbol.iterator 属性。

Iterator(迭代器)

Iterator 必须有 next() 方法,它每次返回一个 {done: Boolean, value: any} 对象,这里 done:true 表明迭代结束,否则 value 就是下一个要迭代的值。

手写实现一个 Iterator

假设我们 range 对象,我们希望对其使用 for..of 循环:

let range = {
  from: 1,
  to: 5
};

// 我们希望可以这样:
// for(let num of range) ... num=1,2,3,4,5 

为了让 range 对象可迭代,我们需要为对象添加一个名为 Symbol.iterator 的方法。当 for..of 循环启动时,它会调用这个方法(如果没找到,就会报错)。这个方法必须返回一个 迭代器(iterator) —— 一个有 next 方法的对象。从此开始,for..of 仅适用于这个被返回的对象。当 for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法。next() 方法返回的结果的格式必须是 {done: Boolean, value: any},当 done=true 时,表示迭代结束,否则 value 是下一个值。

let range = {
  from: 1,
  to: 5
};

// for..of 循环首先会调用这个
range[Symbol.iterator] = function() {

  // 它返回迭代器对象,接下来 for..of 仅与此迭代器一起工作
  return {
    current: this.from,
    last: this.to,

    // next() 在 for..of 的每一轮循环迭代中被调用
    next() {
      // 它将会返回 {done:.., value :...} 格式的对象
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// 现在它可以运行了!
for (let num of range) {
  alert(num); // 1, 然后是 2, 3, 4, 5
}

请注意可迭代对象的核心功能:关注点分离。range 自身没有 next() 方法。相反,是通过调用 range[Symbol.iterator]() 创建了另一个对象,即所谓的“迭代器”对象,并且它的 next 会为迭代生成值。因此,迭代器对象和与其进行迭代的对象是分开的。

从技术上说,我们可以将它们合并,并使用 range 自身作为迭代器来简化代码。

就像这样:

let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  alert(num); // 1, 然后是 2, 3, 4, 5
}

现在 range[Symbol.iterator]() 返回的是 range 对象自身:它包括了必需的 next() 方法,并通过 this.current 记忆了当前的迭代进程。这样更短,对吗?是的。有时这样也可以。

但缺点是,现在不可能同时在对象上运行两个 for..of 循环了:它们将共享迭代状态,因为只有一个迭代器,即对象本身。但是两个并行的 for..of 是很罕见的,即使在异步情况下。

无穷迭 Interator

此外,我们还可以将 range 设置为 range.to = Infinity,这时 range 则成为了无穷迭代器。或者我们可以创建一个可迭代对象,它生成一个无穷伪随机数序列。也是可能的。next 没有什么限制,它可以返回越来越多的值,这是正常的。当然,迭代这种对象的 for..of 循环将不会停止。但是我们可以通过使用 break 来停止它。

Generator

Generator 是一个特殊函数,调用返回一个 Generator。

未完待续……

参考: