Generator 生成器函数的使用
什么是 Generator?
Generator(生成器)是 ES6 引入的一种特殊函数,它可以在执行过程中暂停和恢复,允许我们以更优雅的方式处理异步操作和迭代逻辑。与普通函数不同,Generator 函数可以多次返回值,而不是一次性执行完毕。
基本语法
Generator 函数通过 function* 关键字定义,使用 yield 关键字来暂停执行并返回值:
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
调用 Generator 函数不会立即执行函数体,而是返回一个迭代器对象。每次调用 next() 方法时,函数会执行到下一个 yield 表达式处暂停。
核心概念
1. yield 表达式
yield 关键字用于暂停和恢复生成器函数的执行:
function* numberGenerator() {
console.log('开始执行');
yield 1;
console.log('继续执行');
yield 2;
console.log('最后执行');
return 3;
}
const gen = numberGenerator();
gen.next(); // 输出: 开始执行, 返回 { value: 1, done: false }
gen.next(); // 输出: 继续执行, 返回 { value: 2, done: false }
gen.next(); // 输出: 最后执行, 返回 { value: 3, done: true }
2. next() 方法传参
next() 方法可以接收参数,这个参数会作为上一个 yield 表达式的返回值:
function* communicate() {
const result1 = yield '你好';
console.log('收到:', result1);
const result2 = yield '再见';
console.log('收到:', result2);
}
const gen = communicate();
console.log(gen.next()); // { value: '你好', done: false }
console.log(gen.next('世界')); // 输出: 收到: 世界, 返回 { value: '再见', done: false }
console.log(gen.next('朋友')); // 输出: 收到: 朋友, 返回 { value: undefined, done: true }
3. yield* 表达式
yield* 用于在一个 Generator 中委托给另一个 Generator:
function* generator1() {
yield 1;
yield 2;
}
function* generator2() {
yield 'a';
yield* generator1();
yield 'b';
}
const gen = generator2();
console.log([...gen]); // ['a', 1, 2, 'b']
实际应用场景
1. 实现迭代器
Generator 天然就是迭代器,可以用来自定义迭代行为:
function* range(start, end, step = 1) {
for (let i = start; i < end; i += step) {
yield i;
}
}
// 使用 for...of 遍历
for (let num of range(0, 10, 2)) {
console.log(num); // 0, 2, 4, 6, 8
}
// 转换为数组
const arr = [...range(1, 6)]; // [1, 2, 3, 4, 5]
2. 无限序列
Generator 可以优雅地生成无限序列,只在需要时才计算值:
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
console.log(fib.next().value); // 5
// 获取前 10 个斐波那契数
function* take(n, iterable) {
let count = 0;
for (let item of iterable) {
if (count++ >= n) return;
yield item;
}
}
console.log([...take(10, fibonacci())]);
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
3. 异步流程控制
Generator 可以配合 Promise 实现更清晰的异步代码(这也是 async/await 的基础):
function* fetchUserData() {
try {
const user = yield fetch('/api/user');
const posts = yield fetch(`/api/posts/${user.id}`);
const comments = yield fetch(`/api/comments/${posts[0].id}`);
return { user, posts, comments };
} catch (error) {
console.error('获取数据失败:', error);
}
}
// 简单的执行器
function run(generator) {
const gen = generator();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => res.json())
.then(data => handle(gen.next(data)))
.catch(err => gen.throw(err));
}
return handle(gen.next());
}
run(fetchUserData);
4. 状态机
Generator 非常适合实现状态机:
function* trafficLight() {
while (true) {
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); // 红灯 (循环)
5. 数据流处理
可以用 Generator 实现类似管道的数据处理:
function* map(iterable, mapper) {
for (let item of iterable) {
yield mapper(item);
}
}
function* filter(iterable, predicate) {
for (let item of iterable) {
if (predicate(item)) yield item;
}
}
// 使用
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = filter(
map(numbers, x => x * 2),
x => x > 10
);
console.log([...result]); // [12, 14, 16, 18, 20]
Generator 的方法
return() 方法
提前终止 Generator 的执行:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return('结束')); // { value: '结束', done: true }
console.log(g.next()); // { value: undefined, done: true }
throw() 方法
向 Generator 内部抛出错误:
function* errorHandler() {
try {
yield 1;
yield 2;
} catch (e) {
console.log('捕获错误:', e);
}
yield 3;
}
const gen = errorHandler();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.throw('出错了')); // 输出: 捕获错误: 出错了, 返回 { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }
性能优势
Generator 的惰性求值特性可以带来性能优势:
// 普通方法 - 立即计算所有值
function getRange(n) {
const result = [];
for (let i = 0; i < n; i++) {
result.push(i * i);
}
return result;
}
// Generator - 按需计算
function* getSquares(n) {
for (let i = 0; i < n; i++) {
yield i * i;
}
}
// 如果只需要前 3 个值
const squares = getSquares(1000000);
const firstThree = [...take(3, squares)]; // 只计算了 3 个值,而不是 100 万个
注意事项
- Generator 函数必须用 function 声明*,箭头函数不能用作 Generator
- yield 只能在 Generator 函数内部使用,在嵌套函数中使用会报错
- Generator 对象是一次性的,遍历结束后不能重新开始,需要重新创建
- 性能考虑:虽然 Generator 很强大,但对于简单场景,普通函数可能更高效