处理集合中的每个项是很常见的操作。JavaScript 提供了许多迭代集合的方法,从简单的
for
循环到map()
和filter()
。迭代器和生成器将迭代的概念直接带入核心语言,并提供了一种机制来自定义
for...of
循环的行为。
迭代器练习:
const students = ["lisa", "mike", "beak"]
let index = 0
const studentsIterator = {
next: function() {
if(index < students.length) {
return {done: false, value: students[index++] }
} else {
return { done: true, value: undefined }
}
}
}
console.log(studentsIterator.next())
console.log(studentsIterator.next())
console.log(studentsIterator.next())
console.log(studentsIterator.next())
输出如下:
上述代码的弊端:我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;
事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;
若一个对象拥有迭代行为,比如在 for...of
中会循环哪些值,那么那个对象便是一个可迭代对象。一些内置类型,如 Array
或 Map
拥有默认的迭代行为,而其他类型(比如Object
)则没有。
为了实现可迭代,一个对象必须实现 @@iterator 方法,这意味着这个对象(或其原型链中的任意一个对象)必须具有一个带 Symbol.iterator
键(key)的属性。
可以多次迭代一个迭代器,或者只迭代一次。
只能迭代一次的 Iterables(例如 Generators)通常从它们的**@@iterator方法中返回它本身,其中那些可以多次迭代的方法必须在每次调用@@iterator**时返回一个新的迭代器。
举个栗子:将Object
变成可迭代对象
// 将info变成可迭代对象
/*
1.必须实现一个特定的函数: [Symbol.iterator]
2.这个函数需要返回一个迭代器(这个迭代器用于迭代当前的对象)
*/
const info = {
students: ["lisa", "beak", "chen"],
[Symbol.iterator]: function () {
let index = 0
const infoIterator = {
next: function () {
if (index < info.students.length) {
return { done: false, value: this.students[index++] }
} else {
return { done: true, value: undefined }
}
}
}
return infoIterator
}
}
// 给info创建一个迭代器, 迭代info中的students
console.log(infoIterator.next())
console.log(infoIterator.next())
console.log(infoIterator.next())
console.log(infoIterator.next())
// 可迭代对象必然具备下面的特点
const iterator = infos[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
// 可迭对象可以进行for of操作
for (const item of infos) {
console.log(item)
}
应用场景:
for ...of
、展开语法(spread syntax)、yield*
、解构赋值(Destructuring_assignment);new Map([Iterable])
、new WeakMap([iterable])
、new Set([iterable])
、new WeakSet([iterable])
;Promise.all(iterable)
、Promise.race(iterable)
、Array.from(iterable)
虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。
生成器函数提供了一个强大的选择:
- 它允许你定义一个包含自有迭代算法的函数
- 同时它可以自动维护自己的状态
举个栗子:
// 1.定义了一个生成器函数
function* fn() {
console.log("1")
console.log("2")
yield
console.log("3")
console.log("4")
yield
console.log("5")
console.log("6")
}
// 2.调用生成器函数, 返回一个 生成器对象
const generator = fn()
// 调用next方法
generator.next()
generator.next()
generator.next()
既然生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
const students = ["lisa", "mike", "beak"]
function* arrayIterator(arr) {
for (const item of arr) {
yield item
}
}
const studentsIterator = arrayIterator(students)
console.log(studentsIterator.next())
console.log(studentsIterator.next())
console.log(studentsIterator.next())
console.log(studentsIterator.next())