myfreax

JavaScript generator 生成器

在 JavaScript ,普通函数一旦运行就会直接运行到函数的结束才会退出函数。它不能中途暂停然后从暂停的地方继续而生成器则可以做到这一点

JavaScript generator 生成器
JavaScript generator 生成器

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

内容导航