摘要:在本教程中,您将了解 JavaScript 生成器以及如何有效地使用它们。
JavaScript 生成器简介
在 JavaScript 中,一个普通的 函数 是根据运行至完成模型执行的。它不能中途暂停,然后从暂停的地方继续。例如
function foo() {
console.log('I');
console.log('cannot');
console.log('pause');
}Code language: JavaScript (javascript)foo() 函数从上到下执行。退出 foo() 的唯一方法是返回它或抛出错误。如果您再次调用 foo() 函数,它将从上到下开始执行。
foo();输出
I
cannot
pauseES6 引入了一种与普通函数不同的新型函数:函数生成器或生成器。
生成器可以中途暂停,然后从暂停的地方继续。例如
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循环一起使用。