JavaScript中的迭代器和迭代器简介
JavaScript 支持一种协议,通过该协议,诸如数组之类的对象可以被控制结构使用,例如 for...of 和扩展运算符 ...
以顺序循环数据。 这称为可迭代对象,支持此功能的数据结构称为可迭代对象。 虽然 JavaScript 从一开始就提供了具有可迭代属性的映射、数组和集合,但普通对象默认情况下没有这个。
Iterables 是一种数据结构,它提供了一种机制,允许其他数据消费者以顺序方式公开访问其元素。 想象一个自打包的数据结构,当放入 for...of
循环时,它会按顺序逐一卸载数据。
可迭代协议的概念可以分为 iterable(数据结构本身)和 iterator(一种在可迭代对象上移动的指针)。 让我们以一个数组为例,当该数组在 for...of
循环中使用时,将调用 iterable 属性,该属性返回一个 iterator
。 这个可迭代的属性被命名为 Symbol.iterator
并且它返回的对象可以在所有循环控制结构共享的公共接口上使用。
在某种程度上,可以将 Symbol.iterator
与迭代器工厂进行比较,只要将数据结构放在循环中,它就会生成一个迭代器。
当迭代器在数据结构上移动并按顺序提供元素时,可迭代对象返回的对象包含 value
和 done
属性。
该值表示迭代器指向的当前数据值,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.iterator
从 Reptiles
获取迭代器,如下所示:
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。