JavaScript中的迭代器和迭代器简介

来自菜鸟教程
跳转至:导航、​搜索

JavaScript 支持一种协议,通过该协议,诸如数组之类的对象可以被控制结构使用,例如 for...of 和扩展运算符 ... 以顺序循环数据。 这称为可迭代对象,支持此功能的数据结构称为可迭代对象。 虽然 JavaScript 从一开始就提供了具有可迭代属性的映射、数组和集合,但普通对象默认情况下没有这个。

Iterables 是一种数据结构,它提供了一种机制,允许其他数据消费者以顺序方式公开访问其元素。 想象一个自打包的数据结构,当放入 for...of 循环时,它会按顺序逐一卸载数据。

可迭代协议的概念可以分为 iterable(数据结构本身)和 iterator(一种在可迭代对象上移动的指针)。 让我们以一个数组为例,当该数组在 for...of 循环中使用时,将调用 iterable 属性,该属性返回一个 iterator。 这个可迭代的属性被命名为 Symbol.iterator 并且它返回的对象可以在所有循环控制结构共享的公共接口上使用。

在某种程度上,可以将 Symbol.iterator 与迭代器工厂进行比较,只要将数据结构放在循环中,它就会生成一个迭代器。

当迭代器在数据结构上移动并按顺序提供元素时,可迭代对象返回的对象包含 valuedone 属性。

该值表示迭代器指向的当前数据值,done 是一个布尔值,告诉我们迭代器是否已到达数据结构中的最后一个元素。

这个 {value, done} 被循环等结构消耗。 那么迭代器方法是如何调用下一个对象的呢? 使用 Symbol.iterator() 方法中定义的 next() 方法。

在这一点上,我可以对迭代器属性进行更好的定义是,它是 知道 如何逐个访问集合中的元素并提供停止这样做的逻辑规则(例如. 如果数组中没有更多元素)。

对象和迭代

JavaScript 对象很酷,但为什么它们没有可迭代对象? 好吧,其中一些原因可能是:

  • 对象的主要特征之一是它是用户定义的。 因此,将无声的 [Symbol.iterator]() 滑入对象会造成令人讨厌的惊喜。
  • 考虑到所有对象组成可能不相似,以上几点也意味着它可以由用户手动添加。 所以拥有一个通用的可迭代属性是毫无意义的。
  • 如果要循环对象中的顶级元素,请使用另一个人:for...in 循环。
  • Maps 对象类型的使用可能更合适。

除了最后一点(我不想承认我太习惯于常规对象而无法移动到地图)以上所有要点都是在对象中不包含可迭代对象的好理由,但是如果您的老板希望您的 JavaScript 对象具有一个呢?

对象的简单可迭代实现如下所示:

let Reptiles = {
  biomes: {
    water: ["Alligators", "Crocs"],
    land: ["Snakes", "Turtles"]
  },

  [Symbol.iterator]() {
    let reptilesByBiome = Object.values(this.biomes);
    let reptileIndex = 0;
    let biomeIndex = 0;
    return {
      next() {
        if (reptileIndex >= reptilesByBiome[biomeIndex].length) {
          biomeIndex++;
          reptileIndex = 0;
        }

        if (biomeIndex >= reptilesByBiome.length) {
          return { value: undefined, done: true };
        }

        return {
          value: reptilesByBiome[biomeIndex][reptileIndex++],
          done: false
        };
      }
    };
  }
};

// let's now iterate over our new `Reptiles` iterable:
for (let reptile of Reptiles) console.log(reptile);

输出将是:

Alligators
Crocs
Snakes
Turtles

通过这个例子,我们看到迭代器可以在对象中实现。 Iterables 可以成为对象的强大属性,在处理某些情况时提供易用性,并帮助我们避免编写长路径名。

进入迭代器

for...of 这样的循环有一个内置机制来消耗迭代,直到 done 值评估为真。 如果您想在没有内置循环的情况下自行使用可迭代对象怎么办? 很简单,您从可迭代对象中获取迭代器,然后手动调用 next()

给定与上面相同的示例,我们可以通过调用 Symbol.iteratorReptiles 获取迭代器,如下所示:

let reptileIterator = Reptiles[Symbol.iterator]();

然后,您可以像这样使用迭代器:

console.log(reptileIterator.next());
// {value: "Alligators", done: false}
console.log(reptileIterator.next());
// {value: "Crocs", done: false}
console.log(reptileIterator.next());
// {value: "Snakes", done: false}
console.log(reptileIterator.next());
// {value: "Turtles", done: false}
console.log(reptileIterator.next());
// {value: undefined, done: true}

console.log(reptileIterator.next());
// TypeError: Cannot read property 'length' of undefined

如您所见,迭代器有一个 next() 方法,该方法返回可迭代对象中的下一个值。 一旦返回最后一个值, done 的值只会在另一个 next() 调用之后评估为 true,因此要遍历整个可迭代对象,总会再调用一次next() 比 iterable 中有数据。 在迭代器到达可迭代对象的末尾后再次调用 next() 将导致抛出 TypeError

包起来

我希望这篇介绍能让您大开眼界,让您更多地了解 JavaScript 的数据结构(例如对象)的内部结构。 这只是皮毛,如果您想了解更多信息,我邀请您阅读 Kyle Simpson 的 优秀的 章节关于 Iterables