阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 生成器函数语法

生成器函数语法

作者:陈川 阅读数:18565人阅读 分类: JavaScript

生成器函数的基本概念

ECMAScript 6引入的生成器函数是一种特殊的函数,它可以暂停执行并在稍后恢复。这种函数通过function*语法定义,使用yield关键字来暂停执行并返回一个值。生成器函数在被调用时不会立即执行函数体,而是返回一个生成器对象。

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

const gen = simpleGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3

生成器函数的声明方式

生成器函数有两种主要的声明方式:

  1. 函数声明形式:
function* generatorFunction() {
  // 函数体
}
  1. 函数表达式形式:
const generatorFunction = function*() {
  // 函数体
};

还可以作为对象的方法:

const obj = {
  *generatorMethod() {
    // 方法体
  }
};

yield关键字的工作原理

yield关键字是生成器函数的核心特性,它有两个主要作用:

  1. 暂停生成器函数的执行
  2. 向生成器外部返回一个值

每次调用生成器的next()方法时,函数会从上次暂停的位置继续执行,直到遇到下一个yield表达式或函数结束。

function* countToThree() {
  console.log('开始执行');
  yield 1;
  console.log('继续执行');
  yield 2;
  console.log('即将结束');
  yield 3;
  console.log('执行完毕');
}

const counter = countToThree();
counter.next(); // 输出"开始执行",返回{value: 1, done: false}
counter.next(); // 输出"继续执行",返回{value: 2, done: false}
counter.next(); // 输出"即将结束",返回{value: 3, done: false}
counter.next(); // 输出"执行完毕",返回{value: undefined, done: true}

生成器对象的迭代协议

生成器对象实现了迭代器协议,这意味着它们可以用于for...of循环和其他接受可迭代对象的场合。

function* alphabetGenerator() {
  yield 'a';
  yield 'b';
  yield 'c';
}

for (const letter of alphabetGenerator()) {
  console.log(letter); // 依次输出'a', 'b', 'c'
}

yield*表达式

yield*表达式用于委托给另一个生成器或可迭代对象,可以简化生成器函数的编写。

function* innerGenerator() {
  yield 1;
  yield 2;
}

function* outerGenerator() {
  yield '开始';
  yield* innerGenerator();
  yield '结束';
}

const gen = outerGenerator();
console.log(gen.next().value); // '开始'
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // '结束'

生成器函数的双向通信

生成器函数不仅可以通过yield向外传递值,还可以通过next()方法接收外部传入的值。

function* twoWayGenerator() {
  const name = yield '你的名字是什么?';
  const age = yield `你好,${name}!你的年龄是多少?`;
  return `信息:${name}, ${age}岁`;
}

const gen = twoWayGenerator();
console.log(gen.next().value); // "你的名字是什么?"
console.log(gen.next('张三').value); // "你好,张三!你的年龄是多少?"
console.log(gen.next(25).value); // "信息:张三, 25岁"

生成器函数的错误处理

生成器函数可以通过throw()方法从外部抛出错误,并在内部使用try...catch捕获。

function* errorHandlingGenerator() {
  try {
    yield 1;
    yield 2;
  } catch (error) {
    console.log('捕获错误:', error);
    yield 3;
  }
}

const gen = errorHandlingGenerator();
console.log(gen.next().value); // 1
console.log(gen.throw('出错了!').value); // 输出"捕获错误: 出错了!",然后返回3

生成器函数的提前终止

可以使用return()方法提前终止生成器函数的执行。

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

const gen = earlyTerminationGenerator();
console.log(gen.next().value); // 1
console.log(gen.return('提前结束').value); // "提前结束"
console.log(gen.next().value); // undefined

生成器函数的实际应用场景

  1. 惰性求值:只在需要时才计算值
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
  1. 异步流程控制:简化异步操作的处理
function* asyncFlow() {
  const data1 = yield fetchData1();
  const data2 = yield fetchData2(data1);
  const result = yield processData(data2);
  return result;
}

// 配合运行器函数使用
function runGenerator(gen) {
  const iterator = gen();
  
  function iterate(iteration) {
    if (iteration.done) return iteration.value;
    const promise = iteration.value;
    return promise.then(x => iterate(iterator.next(x)));
  }
  
  return iterate(iterator.next());
}
  1. 状态机实现:清晰表达有限状态机
function* trafficLight() {
  while (true) {
    yield '红灯';
    yield '黄灯';
    yield '绿灯';
    yield '黄灯';
  }
}

const light = trafficLight();
console.log(light.next().value); // "红灯"
console.log(light.next().value); // "黄灯"
console.log(light.next().value); // "绿灯"
console.log(light.next().value); // "黄灯"

生成器函数与异步/await的关系

生成器函数是ES6引入的,而async/await是ES2017引入的语法糖。实际上,async/await可以看作是生成器函数处理异步操作的一种更简洁的方式。

// 使用生成器函数处理异步
function* fetchUserData() {
  const user = yield fetch('/user');
  const posts = yield fetch(`/posts/${user.id}`);
  return { user, posts };
}

// 等效的async/await写法
async function fetchUserData() {
  const user = await fetch('/user');
  const posts = await fetch(`/posts/${user.id}`);
  return { user, posts };
}

生成器函数的性能考虑

虽然生成器函数提供了强大的控制流能力,但在性能敏感的场景中需要注意:

  1. 生成器函数的创建和调用比普通函数开销更大
  2. 频繁的暂停和恢复操作可能影响性能
  3. 在热代码路径中应谨慎使用
// 性能测试示例
function* performanceTest() {
  let sum = 0;
  for (let i = 0; i < 1e6; i++) {
    sum += i;
    yield;
  }
  return sum;
}

const gen = performanceTest();
while (!gen.next().done) {
  // 空循环
}

生成器函数的组合使用

多个生成器函数可以组合使用,创建更复杂的控制流。

function* generatorA() {
  yield 'A1';
  yield 'A2';
}

function* generatorB() {
  yield 'B1';
  yield* generatorA();
  yield 'B2';
}

function* generatorC() {
  yield* generatorB();
  yield 'C1';
}

const gen = generatorC();
console.log(gen.next().value); // 'B1'
console.log(gen.next().value); // 'A1'
console.log(gen.next().value); // 'A2'
console.log(gen.next().value); // 'B2'
console.log(gen.next().value); // 'C1'

生成器函数与迭代器的关系

所有生成器对象都是迭代器,但并非所有迭代器都是生成器对象。生成器函数提供了一种简便的方式来创建符合迭代器协议的对象。

// 手动实现迭代器
const manualIterator = {
  [Symbol.iterator]() {
    let step = 0;
    return {
      next() {
        step++;
        if (step === 1) return { value: '第一步', done: false };
        if (step === 2) return { value: '第二步', done: false };
        return { value: undefined, done: true };
      }
    };
  }
};

// 使用生成器函数实现相同功能
function* generatorIterator() {
  yield '第一步';
  yield '第二步';
}

// 两者都可以用于for...of循环
for (const step of manualIterator) console.log(step);
for (const step of generatorIterator()) console.log(step);

生成器函数的递归应用

生成器函数可以递归调用自身或其他生成器函数,实现复杂的数据结构遍历。

function* traverseTree(node) {
  if (!node) return;
  yield node.value;
  if (node.left) yield* traverseTree(node.left);
  if (node.right) yield* traverseTree(node.right);
}

const tree = {
  value: 1,
  left: {
    value: 2,
    left: { value: 4 },
    right: { value: 5 }
  },
  right: {
    value: 3,
    left: { value: 6 },
    right: { value: 7 }
  }
};

for (const value of traverseTree(tree)) {
  console.log(value); // 1, 2, 4, 5, 3, 6, 7
}

生成器函数的高级模式

  1. 协程模式:多个生成器函数协同工作
function* producer() {
  while (true) {
    const value = Math.random();
    yield value;
  }
}

function* consumer() {
  let sum = 0;
  const prod = producer();
  for (let i = 0; i < 10; i++) {
    const { value } = prod.next();
    sum += value;
    yield sum / (i + 1);
  }
}

const cons = consumer();
for (let i = 0; i < 10; i++) {
  console.log(cons.next().value);
}
  1. 无限序列生成
function* naturalNumbers() {
  let n = 0;
  while (true) {
    yield n++;
  }
}

function* take(n, iterable) {
  let count = 0;
  for (const item of iterable) {
    if (count++ >= n) break;
    yield item;
  }
}

// 获取前5个自然数
for (const num of take(5, naturalNumbers())) {
  console.log(num); // 0, 1, 2, 3, 4
}

生成器函数的调试技巧

调试生成器函数时需要注意暂停点的位置,可以使用以下技巧:

  1. yield语句前后添加日志
  2. 使用调试器的步进功能
  3. 为生成器对象添加自定义的toString()方法
function* debugGenerator() {
  console.log('开始执行');
  yield '第一步';
  console.log('继续执行');
  yield '第二步';
  console.log('即将结束');
  yield '第三步';
  console.log('执行完毕');
}

const debugGen = debugGenerator();
debugGen.next(); // 输出"开始执行"
debugGen.next(); // 输出"继续执行"
debugGen.next(); // 输出"即将结束"
debugGen.next(); // 输出"执行完毕"

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益,请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:for...of循环原理

下一篇:yield关键字

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌