11.JavaScript生成器函数

257 阅读3分钟

JavaScript生成器函数

在JavaScript中,生成器函数是一个可以暂停和稍后恢复的函数,允许它随着时间的推移产生一系列值,而不是一次计算它们并在单个数组中返回它们。

这是一个生成数字序列的生成器函数的简单示例:

function* numbers() {
  yield 1;
  yield 2;
  yield 3;
}

const generator = numbers();

console.log(generator.next().value); // 1
console.log(generator.next().value); // 2
console.log(generator.next().value); // 3

要创建生成器函数,您可以使用function*​语法,然后使用​yield​关键字生成一个值。每次迭代生成器(例如,使用for​循环)时,它将执行代码,直到下一个​yield​语句,然后暂停。当生成器再次迭代时,它将从中断的地方继续执行,直到下一个​yield​语句。

生成器函数对于生成大型数据序列非常有用,因为它们允许您一次生成一个值,而不是一次生成所有值。这可以更节省内存,也可以允许您在生成值时处理它们,而不必等待整个序列被计算后才能开始处理它。

以下是JavaScript中生成器函数的一些用例示例:

  • 迭代大型数据集:如果您有一个大型数据集,您希望一次处理一个项目,则可以使用生成器函数在处理数据集中的每个项目时产生它,而不是一次将整个数据集读入内存。
  • 实现无限序列:生成器函数可用于创建无限序列,例如无限范围的数字或无限随机数流。
  • 实现异步代码:生成器函数可以用来以看起来同步的风格编写异步代码,使用​yield​关键字暂停生成器函数,使用​next()​方法恢复它。这可以使异步代码更容易读写。
  • 实现惰性求值:生成器函数可用于实现惰性求值,即在需要时才计算序列中的下一个值。这对于优化某些类型算法的性能很有用。

这是一个生成无限随机数序列的生成器函数示例:

function* randomNumbers() {
  while (true) {
    yield Math.random();
  }
}

const generator = randomNumbers();

console.log(generator.next().value); // 0.123456789
console.log(generator.next().value); // 0.987654321
console.log(generator.next().value); // 0.654321987

将参数传递给生成器函数

要在JavaScript中将参数传递给生成器函数,您可以使用​next()​方法并将参数作为参数传递给​next()​方法。

这是一个接受两个参数的生成器函数示例:

function* add(x, y) {
  const result = x + y;
  yield result;
}

const generator = add(10, 20);

console.log(generator.next().value); // 30

要在每次迭代时将不同的参数传递给生成器函数,您可以使用循环并在循环内使用所需的参数调用​next()​方法。

这是一个生成器函数的棘手示例,它接受两个参数并生成将它们相加的结果以获取一系列输入值:

function* add(x, y) {
  while (true) {
    const result = x + y;
    x = yield result;
    y = yield result;
  }
}

const generator = add(10, 20);

for (let i = 1; i <= 5; i++) {
  console.log(generator.next(i * 10, i * 20).value);
}

// Output:
// 30
// 30
// 50
// 50
// 90

在这个例子中,生成器函数有一个无限循环,并使用​yield​关键字暂停生成器并返回一个值。​next()​方法在循环内部调用,x​和y​的新值作为参数,这会导致生成器函数恢复执行并计算下一个结果。

上面的例子很棘手,因为我们向​next()​方法传递了两个参数,这可能会让您感到困惑。当谈到生成函数和传递参数时,我们需要在这里了解一些事情:

  • 生成器函数可以退出,稍后重新输入。
  • 变量绑定将保存在所有重新入口中。
  • 调用生成器函数不会立即执行该函数。
  • 当我们调用​next()​方法时,函数的主体会被执行,直到执行达到​yield​。
  • next()​方法只接受一个参数。所有其他参数都将被忽略。
  • 我们使用​next()​方法参数传递的值将替换暂停执行的​yield​表达式。

现在有了上面所有这些理解,让我们浏览一下代码。

在迭代1

  • x是10,y是20
  • 它在x=yield result处停止,控制台中的结果为30。

在迭代2

  • 这里我们调用next()​方法,而不是add()​方法
  • 继续执行
  • x是20,因为参数是i*10(i是2)
  • y保持旧值为20(next方法的第二个参数被忽略)
  • 它在y=yield result时停止;控制台中的结果又是30(携带旧的求和)

在迭代3

  • 继续执行
  • x是20并继续
  • y是30并且继续
  • 它在x=yield result 时停止
  • 所以结果是50。控制台打印50

在迭代4

  • 继续执行
  • 现在next()将替换x值,因为它被停止了x。所以新的x值是4*10,也就是40。
  • y仍然是30
  • 但是没有计算新的结果。所以打印旧的结果50

在迭代5

  • 继续执行
  • x仍然是40,但现在y将从next变为5*10的值,即50
  • 新的结果表达式执行并产生90

请记住:在所有这些迭代中,next()方法的第二个参数没有任何作用。它被忽略了。

Generators and Promises

在JavaScript中,生成器(generator)函数可以与Promises结合使用,以同步样式处理异步代码。

以下是使用生成器函数和Promise从远程API异步获取数据的示例:

function* fetchData() {
  const response = yield fetch('https://example.com/api/data');
  const data = yield response.json();
  return data;
}

function runGenerator(generator) {
  const iterator = generator();

  function iterate(iteration) {
    if (iteration.done) return iteration.value;
    const promise = iteration.value;
    return promise.then(result => iterate(iterator.next(result)));
  }

  return iterate(iterator.next());
}

const dataPromise = runGenerator(fetchData);

dataPromise.then(data => {
  console.log(data);
});

在这个例子中,​fetchData​生成器函数使用​yield​关键字暂停执行,并返回一个Promise,表示从远程API获取数据的异步操作。​runGenerator​函数是一个辅助函数,它接受一个生成器函数并运行它,遍历生成器,并用​then()​方法解析生成器函数返回的Promises。

这允许您以看起来同步的风格编写异步代码,使用yield​关键字暂停generator(生成器)函数,使用next()​方法恢复它。这可以使异步代码更易于读写。