JavaScript generator 生成器
在 JavaScript ,普通函数一旦运行就会直接运行到函数的结束才会退出函数。它不能中途暂停然后从暂停的地方继续而生成器则可以做到这一点
在本教程中,您将了解 JavaScript 生成器以及如何有效地使用生成器 generator。
JavaScript 生成器简介
在 JavaScript ,普通函数一旦运行就会直接运行到函数的结束才会退出函数。它不能中途暂停然后从暂停的地方继续。例如:
function foo() {
console.log('I');
console.log('cannot');
console.log('pause');
}
foo()
函数从上到下执行。退出的唯一方法是从 foo()
函数返回或抛出错误。如果再次调用 foo()
函数,它将从上到下开始执行。
foo();
输出:
I
cannot
pause
ES6 引入一种与普通函数不同的函数:生成器函数或生成器。
生成器可以中途暂停,然后从暂停处继续。例如:
function* generate() {
console.log('invoked 1st time');
yield 1;
console.log('invoked 2nd time');
yield 2;
}
让我们详细研究一下 generate()
函数。
- 首先,您会在
function
关键字后面看到星号*
。星号表示generate()
它是一个生成器,而不是一个普通函数。 - 其次,
yield
语句返回一个值并暂停函数的执行。
以下代码调用生成器 generate()
:
let gen = generate();
当您调用 generate()
生成器时:
- 首先,您在控制台中看不到任何内容。如果
generate()
是一个普通函数,您会期望看到一些消息。 - 其次,你可以从
generate()
返回值中得到一些东西。
让我们在控制台上打印返回值:
console.log(gen);
输出:
Object [Generator] {}
因此,生成器在调用时返回一个 Generator
对象而不执行其主体。
Generator
对象返回另一个具有两个属性的对象:done
和 value
。换句话说,Generator
对象是可迭代的。
以下调用 Generator
对象的 next()
方法:
let result = gen.next();
console.log(result);
输出:
invoked 1st time
{ value: 1, done: false }
正如您所看到的,Generator 对象执行其主体,在第 1 行打印消息 'invoked 1st time'
并在第 2 行返回值 1。yield
语句返回 1 并在第 2 行暂停生成器。
同样,下面的代码第二次调用 Generator 的 next()
方法:
result = gen.next();
console.log(result);
输出:
invoked 2nd time
{ value: 2, done: false }
这次,生成器从第 3 行恢复执行,输出消息 'invoked 2nd time'
并返回 2。
下面第三次调用生成器对象的 next()
方法:
result = gen.next();
console.log(result);
输出:
{ value: undefined, done: true }
由于生成器是可迭代的,因此您可以使用 for...of
循环:
for (const g of gen) {
console.log(g);
}
这是输出:
invoked 1st time
1
invoked 2nd time
2
JavaScript 生成器示例
以下示例说明了如何使用生成器生成无限的序列:
function* forever() {
let index = 0;
while (true) {
yield index++;
}
}
let f = forever();
console.log(f.next()); // 0
console.log(f.next()); // 1
console.log(f.next()); // 2
每次调用 forever
生成器的 next()
方法时,它都会返回从 0 开始的序列的下一个数字。
使用生成器来实现迭代器
当你实现迭代器时,你必须手动定义 next()
方法。在 next()
方法,您还必须手动保存当前元素的状态。
由于生成器是可迭代的,因此它们可以帮助您简化实现迭代器的代码。
以下是迭代器教程中创建的 Sequence
迭代器 :
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
[Symbol.iterator]() {
let counter = 0;
let nextIndex = this.start;
return {
next: () => {
if ( nextIndex < this.end ) {
let result = { value: nextIndex, done: false }
nextIndex += this.interval;
counter++;
return result;
}
return { value: counter, done: true };
}
}
}
}
这是使用生成器的 Sequence
迭代器:
class Sequence {
constructor( start = 0, end = Infinity, interval = 1 ) {
this.start = start;
this.end = end;
this.interval = interval;
}
* [Symbol.iterator]() {
for( let index = this.start; index <= this.end; index += this.interval ) {
yield index;
}
}
}
正如您所看到的,通过使用生成器,Symbol.iterator 方法要简单得多。
以下脚本使用序列迭代器生成从 1 到 10 的奇数序列:
let oddNumbers = new Sequence(1, 10, 2);
for (const num of oddNumbers) {
console.log(num);
}
输出:
1
3
5
7
9
使用生成器实现 Bag 数据结构
Bag 是一种能够收集元素并迭代元素的数据结构。它不支持删除元素。下面的脚本实现 Bag
数据结构:
class Bag {
constructor() {
this.elements = [];
}
isEmpty() {
return this.elements.length === 0;
}
add(element) {
this.elements.push(element);
}
* [Symbol.iterator]() {
for (let element of this.elements) {
yield element;
}
}
}
let bag = new Bag();
bag.add(1);
bag.add(2);
bag.add(3);
for (let e of bag) {
console.log(e);
}
输出:
1
2
3
结论
生成器是由生成器函数 function* f(){}
创建的。生成器在被调用时不会立即执行其主体。
生成器可以中途暂停并在暂停处恢复执行。 yield
语句暂停生成器的执行并返回一个值。生成器是可迭代的,因此您可以将它们与 for...of
循环一起使用。