Iterable object(可迭代对象)

优质
小牛编辑
144浏览
2023-12-01

可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。

数组是可迭代的。但不仅仅是数组。很多其他内建对象也都是可迭代的。例如字符串也是可迭代的。

如果从技术上讲,对象不是数组,而是表示某物的集合(列表,集合),for..of 是一个能够遍历它的很好的语法,因此,让我们来看看如何使其发挥作用。

Symbol.iterator

通过自己创建一个对象,我们就可以轻松地掌握可迭代的概念。

例如,我们有一个对象,它并不是数组,但是看上去很适合使用 for..of 循环。

比如一个 range 对象,它代表了一个数字区间:

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

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

为了让 range 对象可迭代(也就让 for..of 可以运行)我们需要为对象添加一个名为 Symbol.iterator 的方法(一个专门用于使对象可迭代的内置 symbol)。

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

这是带有注释的 range 的完整实现:

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

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

  // ……它返回迭代器对象(iterator object):
  // 2. 接下来,for..of 仅与此迭代器一起工作,要求它提供下一个值
  return {
    current: this.from,
    last: this.to,

    // 3. next() 在 for..of 的每一轮循环迭代中被调用
    next() {
      // 4. 它将会返回 {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 是很罕见的,即使在异步情况下。

无穷迭代器(iterator)

无穷迭代器也是可能的。例如,将 range 设置为 range.to = Infinity,这时 range 则成为了无穷迭代器。或者我们可以创建一个可迭代对象,它生成一个无穷伪随机数序列。也是可能的。

next 没有什么限制,它可以返回越来越多的值,这是正常的。

当然,迭代这种对象的 for..of 循环将不会停止。但是我们可以通过使用 break 来停止它。

字符串是可迭代的

数组和字符串是使用最广泛的内建可迭代对象。

对于一个字符串,for..of 遍历它的每个字符:

for (let char of "test") {
  // 触发 4 次,每个字符一次
  alert( char ); // t, then e, then s, then t
}

对于代理对(surrogate pairs),它也能正常工作!(译注:这里的代理对也就指的是 UTF-16 的扩展字符)

let str = '