什么是生成器?
生成器是由 function*
声明创建的特殊函数,它返回一个 Generator 对象,该对象符合 可迭代协议 和 迭代器协议。生成器函数可以在执行过程中暂停,并在稍后恢复,其上下文(包括变量绑定)会在恢复时保持不变。
基本语法
function* name(param0, param1, /* …, */ paramN) {
statements
}
特点
yield
关键字:用于暂停和恢复生成器函数的执行。next()
方法:用于恢复生成器的执行,返回一个对象{ value, done }
。value
:当前yield
表达式的值。done
:布尔值,表示生成器是否完成。
return
语句:结束生成器的执行,并返回一个值。yield*
表达式:委托给另一个生成器或可迭代对象。
示例讲解
1. 基本用法
function* generator(i) {
yield i;
yield i + 10;
}
const gen = generator(10);
console.log(gen.next()); // { value: 10, done: false }
console.log(gen.next()); // { value: 20, done: false }
console.log(gen.next()); // { value: undefined, done: true }
2. for...of
遍历生成器
function* foo() {
yield 1;
yield 2;
yield 3;
return 4; // `return` 会结束迭代
}
let result = '';
for (const value of foo()) {
result += value; // 只会输出 1, 2, 3
}
console.log(result); // "123"
3. 无限生成器
function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
const genId = idMaker();
console.log(genId.next().value); // 0
console.log(genId.next().value); // 1
console.log(genId.next().value); // 2
4. yield*
委托
function* add3Gen(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* genAdd10(k) {
yield k;
yield* add3Gen(k); // 委托给另一个生成器
yield k + 10;
}
const genAdd = genAdd10(10);
console.log([...genAdd]); // [10, 11, 12, 13, 20]
5. 传递参数给生成器
function* logGenerator() {
console.log(0);
console.log(1, yield);
console.log(2, yield);
console.log(3, yield);
}
const logGen = logGenerator();
logGen.next(); // 输出 0
logGen.next('a'); // 输出 1 a
logGen.next('b'); // 输出 2 b
logGen.next('c'); // 输出 3 c
6. 生成器中的 return
function* bar() {
yield 1;
return 2; // 结束生成器
yield 3; // 不会执行
}
const barGen = bar();
console.log(barGen.next()); // { value: 1, done: false }
console.log(barGen.next()); // { value: 2, done: true }
console.log(barGen.next()); // { value: undefined, done: true }
生成器的高级用法
1. 作为对象属性
const obj = {
*generator() {
yield 1;
yield 2;
yield 3;
}
};
const objGen = obj.generator();
console.log([...objGen]); // [1, 2, 3]
2. 作为类的方法
class Tool {
*generator() {
yield 1;
yield 2;
yield 3;
}
}
const tool = new Tool();
console.log([...tool.generator()]); // [1, 2, 3]
3. 作为计算属性
class Obj1 {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
const Obj2 = {
*[Symbol.iterator]() {
yield 4;
yield 5;
yield 6;
}
};
console.log(Array.from(new Obj1())); // [1, 2, 3]
console.log(Array.from(Obj2)); // [4, 5, 6]
4. 控制生成器的执行
function* powers(n) {
for (let current = n; ; current *= n) {
yield current;
}
}
for (const power of powers(2)) {
if (power > 1024) break;
console.log(power); // 2, 4, 8, 16, 32, ...
}
5. 实现 async
和 await
async/await
是基于生成器和 Promise
的语法糖,可以通过生成器函数手动实现类似的功能。以下是一个简单的实现示例:
// 示例:使用生成器实现异步逻辑
// 定义一个生成器函数,用于模拟异步数据获取
function* fetchData() {
// 第一次暂停点:模拟异步获取数据1
const data1 = yield new Promise(resolve => setTimeout(() => resolve('数据1'), 1000));
console.log(data1); // 输出数据1
// 第二次暂停点:模拟异步获取数据2
const data2 = yield new Promise(resolve => setTimeout(() => resolve('数据2'), 1000));
console.log(data2); // 输出数据2
// 返回最终结果
return '完成';
}
// 定义一个辅助函数,用于执行生成器函数并处理异步逻辑
function asyncFunction(generatorFunc) {
return function (...args) {
// 创建生成器对象
const generator = generatorFunc(...args);
// 内部递归函数,用于处理每个 yield 返回的 Promise
function handle(result) {
// 如果生成器执行完成,返回最终结果
if (result.done) return Promise.resolve(result.value);
// 处理当前 yield 返回的 Promise
return Promise.resolve(result.value).then(
// 成功时,将结果传回生成器并继续执行
res => handle(generator.next(res)),
// 失败时,将错误传回生成器并继续执行
err => handle(generator.throw(err))
);
}
// 开始执行生成器函数
return handle(generator.next());
};
}
// 使用辅助函数包装生成器函数
const asyncFetchData = asyncFunction(fetchData);
// 执行异步逻辑,并处理最终结果
asyncFetchData().then(result => console.log(result)); // 输出:数据1, 数据2, 完成
通过上述代码,可以看到生成器函数如何与 Promise
配合,模拟出 async/await
的行为。
注意事项
- 生成器不可构造:不能通过
new
关键字实例化生成器。function* fn() {} // const instance = new fn(); // TypeError: fn is not a constructor
yield
是暂停点:生成器函数在调用时不会立即执行,只有调用next()
才会开始执行。yield*
委托:可以将生成器的控制权交给另一个生成器或可迭代对象。
生成器与异步编程
生成器与 Promise
结合使用,是一种强大的异步编程工具,可以解决回调地狱和控制反转的问题。通过 async/await
,可以更简单地实现类似功能。
总结
生成器是 JavaScript 中非常强大的工具,适用于异步编程、迭代器实现、无限序列生成等场景。通过灵活使用 yield
和 yield*
,可以大大提升代码的可读性和可维护性。