迭代器和生成器
迭代器和生成器将迭代的概念直接引入核心语言,并提供了一种机制来定制 for...of
循环的行为。
有关详细信息,另请参阅
迭代器
在 JavaScript 中,迭代器是一个定义序列并在其终止时可能返回一个值的**对象**。
具体来说,迭代器是任何实现了 迭代器协议 的对象,它具有一个 next()
方法,该方法返回一个具有两个属性的对象
创建后,可以通过反复调用 next()
来显式迭代迭代器对象。迭代迭代器被称为使用迭代器,因为通常只能执行一次。在生成终止值后,对 next()
的额外调用应继续返回 {done: true}
。
JavaScript 中最常见的迭代器是数组迭代器,它按顺序返回相关数组中的每个值。
虽然很容易想象所有迭代器都可以表示为数组,但事实并非如此。数组必须全部分配,但迭代器仅在需要时使用。因此,迭代器可以表示无限大小的序列,例如从 0
到 Infinity
的整数范围。
这里是一个可以实现此功能的示例。它允许创建简单的范围迭代器,该迭代器定义从 start
(包含)到 end
(不包含)以 step
为间隔的整数序列。它的最终返回值是它创建的序列的大小,由变量 iterationCount
跟踪。
function makeRangeIterator(start = 0, end = Infinity, step = 1) {
let nextIndex = start;
let iterationCount = 0;
const rangeIterator = {
next() {
let result;
if (nextIndex < end) {
result = { value: nextIndex, done: false };
nextIndex += step;
iterationCount++;
return result;
}
return { value: iterationCount, done: true };
},
};
return rangeIterator;
}
使用迭代器如下所示
const iter = makeRangeIterator(1, 10, 2);
let result = iter.next();
while (!result.done) {
console.log(result.value); // 1 3 5 7 9
result = iter.next();
}
console.log("Iterated over sequence of size:", result.value); // [5 numbers returned, that took interval in between: 0 to 10]
注意:无法反射地知道特定对象是否是迭代器。如果您需要这样做,请使用 可迭代对象。
生成器函数
虽然自定义迭代器是一个有用的工具,但它们的创建需要仔细编程,因为需要显式维护其内部状态。生成器函数提供了一个强大的替代方案:它们允许您通过编写一个执行非连续的单个函数来定义迭代算法。生成器函数使用 function*
语法编写。
当被调用时,生成器函数不会立即执行其代码。相反,它们返回一种特殊类型的迭代器,称为生成器。当通过调用生成器的 next
方法使用一个值时,生成器函数会执行,直到遇到 yield
关键字。
该函数可以按需调用,每次调用都会返回一个新的生成器。每个生成器只能迭代一次。
我们现在可以修改上面的示例。这段代码的行为相同,但实现更容易编写和阅读。
function* makeRangeIterator(start = 0, end = Infinity, step = 1) {
let iterationCount = 0;
for (let i = start; i < end; i += step) {
iterationCount++;
yield i;
}
return iterationCount;
}
可迭代对象
如果一个对象定义了自己的迭代行为(例如,在 for...of
结构中循环遍历哪些值),则该对象是可迭代的。一些内置类型,如 Array
或 Map
,具有默认迭代行为,而其他类型(如 Object
)则没有。
为了成为可迭代的,对象必须实现 [Symbol.iterator]()
方法。这意味着对象(或其 原型链 上的一个对象)必须具有一个具有 Symbol.iterator
键的属性。
有可能多次或仅迭代一次可迭代对象。这取决于程序员了解哪种情况。
只能迭代一次的可迭代对象(如生成器)通常从其 [Symbol.iterator]()
方法返回 this
,而可以多次迭代的可迭代对象必须在每次调用 [Symbol.iterator]()
时返回一个新的迭代器。
function* makeIterator() {
yield 1;
yield 2;
}
const iter = makeIterator();
for (const itItem of iter) {
console.log(itItem);
}
console.log(iter[Symbol.iterator]() === iter); // true
// This example show us generator(iterator) is iterable object,
// which has the [Symbol.iterator]() method return the `iter` (itself),
// and consequently, the it object can iterate only _once_.
// If we change the [Symbol.iterator]() method of `iter` to a function/generator
// which returns a new iterator/generator object, `iter`
// can iterate many times
iter[Symbol.iterator] = function* () {
yield 2;
yield 1;
};
用户定义的可迭代对象
您可以像这样创建自己的可迭代对象
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
用户定义的可迭代对象可以像往常一样在 for...of
循环或扩展语法中使用。
for (const value of myIterable) {
console.log(value);
}
// 1
// 2
// 3
[...myIterable]; // [1, 2, 3]
内置的可迭代对象
String
、Array
、TypedArray
、Map
和 Set
都是内置的可迭代对象,因为它们的原型对象都具有 Symbol.iterator
方法。
预期可迭代对象的语法
某些语句和表达式期望可迭代对象。例如:for...of
循环、扩展语法、yield*
和 解构 语法。
for (const value of ["a", "b", "c"]) {
console.log(value);
}
// "a"
// "b"
// "c"
[..."abc"];
// ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
gen().next();
// { value: "a", done: false }
[a, b, c] = new Set(["a", "b", "c"]);
a;
// "a"
高级生成器
生成器按需计算其 yield
的值,这使它们能够有效地表示计算成本高的序列(甚至无限序列,如上所示)。
next()
方法还接受一个值,该值可用于修改生成器的内部状态。传递给 next()
的值将由 yield
接收。
注意:传递给 第一次 调用 next()
的值始终被忽略。
以下是使用 next(x)
重新启动序列的斐波那契生成器
function* fibonacci() {
let current = 0;
let next = 1;
while (true) {
const reset = yield current;
[current, next] = [next, next + current];
if (reset) {
current = 0;
next = 1;
}
}
}
const sequence = fibonacci();
console.log(sequence.next().value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
console.log(sequence.next().value); // 3
console.log(sequence.next().value); // 5
console.log(sequence.next().value); // 8
console.log(sequence.next(true).value); // 0
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 1
console.log(sequence.next().value); // 2
您可以通过调用生成器的 throw()
方法并传递它应该抛出的异常值来强制生成器抛出异常。该异常将从生成器的当前挂起上下文抛出,就像当前挂起的 yield
是 throw value
语句一样。
如果异常没有在生成器内被捕获,它将通过对 throw()
的调用传播到上层,并且对 next()
的后续调用将导致 done
属性为 true
。
生成器具有一个 return()
方法,该方法返回给定值并完成生成器本身。