JavaScript 生成器
什么是生成器?
生成器(Generator)是ES6引入的一项革命性特性,它允许我们创建可以暂停执行和恢复的函数。与普通函数不同,生成器函数执行时不会一次性运行到结束,而是可以在特定位置暂停,稍后再从暂停的地方继续执行。
生成器为JavaScript带来了一种全新的编程范式,特别适合处理异步流程和迭代大型数据集。
生成器函数的基础语法
生成器函数使用特殊的语法定义,在function
关键字后面加上星号(*
):
function* myGenerator() {
yield 'Hello';
yield 'World';
return 'End';
}
const gen = myGenerator();
关键点解析
function*
声明了一个生成器函数yield
关键字用于暂停函数执行并返回一个值- 调用生成器函数不会执行函数体,而是返回一个生成器对象
生成器的基本使用
生成器对象可以通过next()
方法逐步执行:
function* countToThree() {
yield 1;
yield 2;
yield 3;
}
const generator = countToThree();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: false }
console.log(generator.next()); // { value: undefined, done: true }
next()
方法返回一个包含两个属性的对象:
value
: yield表达式产生的值done
: 一个布尔值,表示生成器是否已完成执行
yield表达式的工作原理
yield
是生成器函数的核心,它有两个作用:
- 暂停生成器函数的执行
- 向调用者返回一个值
function* demonstrateYield() {
console.log('开始执行');
yield '第一次暂停';
console.log('继续执行');
yield '第二次暂停';
console.log('最后部分');
return '完成';
}
const gen = demonstrateYield();
// 什么都不会打印,因为函数体还没有开始执行
console.log('第一次调用next()');
console.log(gen.next());
/* 输出:
开始执行
第一次调用next()
{ value: '第一次暂停', done: false }
*/
console.log('第二次调用next()');
console.log(gen.next());
/* 输出:
继续执行
第二次调用next()
{ value: '第二次暂停', done: false }
*/
console.log('第三次调用next()');
console.log(gen.next());
/* 输出:
最后部分
第三次调用next()
{ value: '完成', done: true }
*/
向生成器传递值
next()
方法可以接收一个参数,该参数会作为上一个yield
表达式的结果:
function* interactiveGenerator() {
const x = yield '请输入第一个数字';
const y = yield '请输入第二个数字';
return x + y;
}
const gen = interactiveGenerator();
console.log(gen.next()); // { value: '请输入第一个数字', done: false }
console.log(gen.next(5)); // { value: '请输入第二个数字', done: false }
console.log(gen.next(3)); // { value: 8, done: true }
第一个next()
调用的参数会被忽略,因为此时没有等待接收值的yield表达式。
生成器迭代
生成器对象是可迭代的,可以在for...of
循环中使用:
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
// 获取斐波那契数列的前10个数
const fib = fibonacci();
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}
// 输出: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
更简洁的方式:
function* take(iterable, limit) {
for (const item of iterable) {
if (limit <= 0) return;
yield item;
limit--;
}
}
// 获取斐波那契数列的前10个数
for (const num of take(fibonacci(), 10)) {
console.log(num);
}
生成器的错误处理
生成器提供了throw()
方法,可以在暂停的生成器内部抛出异常:
function* errorHandlingGenerator() {
try {
yield 'normal execution';
yield 'will not reach here if error is thrown';
} catch (e) {
yield `捕获到错误: ${e}`;
}
yield '继续执行';
}
const gen = errorHandlingGenerator();
console.log(gen.next()); // { value: 'normal execution', done: false }
console.log(gen.throw('自定义错误')); // { value: '捕获到错误: 自定义错误', done: false }
console.log(gen.next()); // { value: '继续执行', done: false }
console.log(gen.next()); // { value: undefined, done: true }
提前终止生成器
可以使用return()
方法提前终止生成器的执行:
function* countToFive() {
for (let i = 1; i <= 5; i++) {
yield i;
}
}
const counter = countToFive();
console.log(counter.next()); // { value: 1, done: false }
console.log(counter.next()); // { value: 2, done: false }
console.log(counter.return('提前结束')); // { value: '提前结束', done: true }
console.log(counter.next()); // { value: undefined, done: true }
生成器委托
使用yield*
表达式可以将控制权委托给另一个生成器:
function* generateNumbers() {
yield 1;
yield 2;
}
function* generateLetters() {
yield 'a';
yield 'b';
}
function* generateAll() {
yield* generateNumbers();
yield* generateLetters();
yield '完成';
}
const gen = generateAll();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 'a', done: false }
console.log(gen.next()); // { value: 'b', done: false }
console.log(gen.next()); // { value: '完成', done: false }
console.log(gen.next()); // { value: undefined, done: true }
实际应用场景
1. 惰性计算和无限序列
生成器可以表示无限序列,只在需要时计算值:
function* naturalNumbers() {
let n = 1;
while (true) {
yield n++;
}
}
// 获取前5个自然数的平方
const numbers = naturalNumbers();
const squares = Array.from({ length: 5 }, () => {
const num = numbers.next().value;
return num * num;
});
console.log(squares); // [1, 4, 9, 16, 25]
2. 简化异步操作
生成器可以与Promise结合使用,简化异步代码:
function fetchData(url) {
return fetch(url).then(response => response.json());
}
function* fetchUserData(userId) {
try {
const user = yield fetchData(`/api/users/${userId}`);
const posts = yield fetchData(`/api/posts?userId=${userId}`);
return { user, posts };
} catch (error) {
console.error('数据获取失败:', error);
}
}
// 执行生成器
function runGenerator(generator, ...args) {
const gen = generator(...args);
function handle(result) {
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value)
.then(res => handle(gen.next(res)))
.catch(err => handle(gen.throw(err)));
}
return handle(gen.next());
}
// 使用
runGenerator(fetchUserData, 1)
.then(data => console.log('用户数据:', data))
.catch(err => console.error('错误:', err));
这种模式是async/await的前身,现代JavaScript通常直接使用async/await,但理解生成器有助于理解async/await的工作原理。
3. 状态机实现
生成器非常适合实现状态机:
function* trafficLightFSM() {
while (true) {
yield '红灯';
yield '黄灯';
yield '绿灯';
}
}
const trafficLight = trafficLightFSM();
console.log(trafficLight.next().value); // 红灯
console.log(trafficLight.next().value); // 黄灯
console.log(trafficLight.next().value); // 绿灯
console.log(trafficLight.next().value); // 红灯(循环)
4. 数据流处理
生成器可以用于处理大型数据流,按需处理数据:
function* processLargeFile(file) {
// 假设我们能按行读取文件
let line;
while ((line = file.readLine()) !== null) {
yield line;
}
}
function* filterLines(lines, keyword) {
for (const line of lines) {
if (line.includes(keyword)) {
yield line;
}
}
}
// 使用示例(伪代码)
const file = openLargeFile('data.txt');
const allLines = processLargeFile(file);
const filteredLines = filterLines(allLines, 'important');
for (const line of filteredLines) {
console.log(line);
}
生成器与迭代器协议
生成器实现了迭代器协议,这使得它们可以与其他迭代相关的特性无缝配合:
function* myGenerator() {
yield* [1, 2, 3];
yield* 'abc';
}
const gen = myGenerator();
// 使用扩展运算符
console.log([...gen]); // [1, 2, 3, 'a', 'b', 'c']
// 或者使用解构赋值
const gen2 = myGenerator();
const [a, b, ...rest] = gen2;
console.log(a, b, rest); // 1 2 [3, 'a', 'b', 'c']
总结
JavaScript生成器是一种强大的语言特性,它通过function*
语法和yield
关键字提供了函数执行暂停和恢复的能力。主要优点包括:
- 懒惰求值 - 只在需要时计算值,适合处理大型或无限数据集
- 双向通信 - 通过
next()
和yield
在调用者和生成器之间进行数据传递 - 简化异步编程 - 提供了比回调更直观的异步编程模型
- 资源管理 - 可以按需生成和处理数据,减少内存使用
生成器是掌握现代JavaScript的重要部分,尤其在处理复杂数据流和异步编程时非常有用。
练习与进一步探索
- 创建一个生成器函数,生成斐波那契数列的前20个数
- 实现一个树结构的深度优先遍历生成器
- 使用生成器实现一个简单的任务调度器
- 研究生成器与async/await的关系
- 尝试使用生成器简化一个复杂的异步操作流程
掌握生成器将为你打开JavaScript编程的新视角,帮助你写出更简洁、更强大的代码!