JavaScript 生成器

摘要:在本教程中,您将了解 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 对象返回另一个具有两个属性的对象:donevalue。换句话说,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 循环一起使用。
本教程是否有帮助?