摘要:在本教程中,您将了解 JavaScript 生成器以及如何有效地使用它们。
JavaScript 生成器简介
在 JavaScript 中,一个普通的 函数 是根据运行至完成模型执行的。它不能中途暂停,然后从暂停的地方继续。例如
function foo() {
console.log('I');
console.log('cannot');
console.log('pause');
}
Code language: JavaScript (javascript)
foo()
函数从上到下执行。退出 foo()
的唯一方法是返回它或抛出错误。如果您再次调用 foo()
函数,它将从上到下开始执行。
foo();
输出
I
cannot
pause
ES6 引入了一种与普通函数不同的新型函数:函数生成器或生成器。
生成器可以中途暂停,然后从暂停的地方继续。例如
function* generate() {
console.log('invoked 1st time');
yield 1;
console.log('invoked 2nd time');
yield 2;
}
Code language: JavaScript (javascript)
让我们详细检查 generate()
函数。
- 首先,您在
function
关键字后看到星号 (*
)。星号表示generate()
是一个生成器,而不是一个普通函数。 - 其次,
yield
语句返回一个值并暂停函数的执行。
以下代码调用 generate()
生成器
let gen = generate();
Code language: JavaScript (javascript)
当您调用 generate()
生成器时
- 首先,您在控制台中看不到任何内容。如果
generate()
是一个普通函数,您会期望看到一些消息。 - 其次,您从
generate()
中获得一个返回值。
让我们在控制台上显示返回值
console.log(gen);
Code language: JavaScript (javascript)
输出
Object [Generator] {}
Code language: CSS (css)
所以,生成器在被调用时会返回一个 Generator
对象,而不会执行其主体。
Generator
对象返回另一个具有两个属性的对象:done
和 value
。换句话说,Generator
对象是 可迭代的。
以下代码在 Generator
对象上调用 next()
方法
let result = gen.next();
console.log(result);
Code language: JavaScript (javascript)
输出
invoked 1st time
{ value: 1, done: false }
Code language: CSS (css)
如您所见,Generator 对象执行其主体,该主体在第 1 行输出消息 'invoked 1st time'
并在第 2 行返回值 1。
yield
语句返回 1 并暂停生成器在第 2 行。
类似地,以下代码第二次调用 Generator 的 next()
方法
result = gen.next();
console.log(result);
Code language: JavaScript (javascript)
输出
invoked 2nd time
{ value: 2, done: false }
Code language: CSS (css)
这次 Generator 从第 3 行恢复其执行,该行输出消息 'invoked 2nd time'
并返回(或 yield)2。
以下代码第三次调用生成器对象的 next()
方法
result = gen.next();
console.log(result);
Code language: JavaScript (javascript)
输出
{ value: undefined, done: true }
Code language: CSS (css)
由于生成器是可迭代的,您可以使用 for...of
循环
for (const g of gen) {
console.log(g);
}
Code language: JavaScript (javascript)
以下是输出
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
Code language: JavaScript (javascript)
每次调用 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 };
}
}
}
}
Code language: JavaScript (javascript)
以下是使用生成器的新的 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;
}
}
}
Code language: JavaScript (javascript)
如您所见,通过使用生成器,Symbol.iterator
方法变得更加简单。
以下脚本使用 Sequence 迭代器生成从 1 到 10 的奇数序列
let oddNumbers = new Sequence(1, 10, 2);
for (const num of oddNumbers) {
console.log(num);
}
Code language: JavaScript (javascript)
输出
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);
}
Code language: JavaScript (javascript)
输出
1
2
3
总结
- 生成器由生成器函数
function* f(){}
创建。 - 生成器在被调用时不会立即执行其主体。
- 生成器可以中途暂停并从暂停的地方恢复执行。
yield
语句暂停生成器的执行并返回一个值。 - 生成器是可迭代的,因此您可以将它们与
for...of
循环一起使用。