生成器函数语法
生成器函数的基本概念
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
生成器函数的声明方式
生成器函数有两种主要的声明方式:
- 函数声明形式:
function* generatorFunction() {
// 函数体
}
- 函数表达式形式:
const generatorFunction = function*() {
// 函数体
};
还可以作为对象的方法:
const obj = {
*generatorMethod() {
// 方法体
}
};
yield关键字的工作原理
yield
关键字是生成器函数的核心特性,它有两个主要作用:
- 暂停生成器函数的执行
- 向生成器外部返回一个值
每次调用生成器的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
生成器函数的实际应用场景
- 惰性求值:只在需要时才计算值
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
- 异步流程控制:简化异步操作的处理
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());
}
- 状态机实现:清晰表达有限状态机
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 };
}
生成器函数的性能考虑
虽然生成器函数提供了强大的控制流能力,但在性能敏感的场景中需要注意:
- 生成器函数的创建和调用比普通函数开销更大
- 频繁的暂停和恢复操作可能影响性能
- 在热代码路径中应谨慎使用
// 性能测试示例
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
}
生成器函数的高级模式
- 协程模式:多个生成器函数协同工作
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);
}
- 无限序列生成
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
}
生成器函数的调试技巧
调试生成器函数时需要注意暂停点的位置,可以使用以下技巧:
- 在
yield
语句前后添加日志 - 使用调试器的步进功能
- 为生成器对象添加自定义的
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关键字