JavaScript生成器(Generator)详解:从基础使用到实际场景
Generator是ES6引入的一种特殊函数。简单来说,它能暂停执行,也能继续执行,还可以分批次返回结果。
普通函数一旦调用就会从头执行到尾,而Generator函数就像带了暂停键,可以随时停止,随时继续。
Generator的基本用法
定义Generator函数
在function后面加一个星号,函数内部使用yield关键字来暂停执行:
function* numberGenerator() {
yield 1; // 第一次暂停,返回1
yield 2; // 第二次暂停,返回2
return 3; // 函数结束,返回3
}使用Generator函数
// 创建生成器对象
const gen = numberGenerator();
// 每次调用next()方法,函数就执行到下一个yield或return
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: true }
console.log(gen.next()); // { value: undefined, done: true }可以这样理解:普通函数像自动售货机,一次给出所有结果;Generator函数像抓娃娃机,每次操作只出一个结果。
Generator的工作原理
Generator函数调用时不会立即执行,而是返回一个生成器对象。这个对象记住函数的执行位置。
每次调用next()方法:
从上次暂停的位置继续执行
遇到yield就暂停,返回yield后面的值
遇到return就结束,done变为true
Generator的高级特性
双向通信
可以向Generator函数内部传递数据:
function* chatGenerator() {
const name = yield "请问你叫什么名字?";
yield `你好,${name}!`;
}
const chat = chatGenerator();
console.log(chat.next().value); // "请问你叫什么名字?"
console.log(chat.next("张三").value); // "你好,张三!"错误处理
可以从外部向Generator函数抛出错误:
function* safeGenerator() {
try {
yield "第一步";
yield "第二步";
} catch (error) {
yield `出错:${error.message}`;
}
}
const gen = safeGenerator();
console.log(gen.next().value); // "第一步"
console.log(gen.throw(new Error("网络错误")).value); // "出错:网络错误"委托其他Generator
使用yield*可以委托其他Generator函数执行:
function* gen1() {
yield 1;
yield 2;
}
function* gen2() {
yield* gen1(); // 执行gen1
yield 3;
}
const result = [...gen2()]; // [1, 2, 3]Generator的实际应用场景
简化异步编程
在async/await出现之前,Generator常用于处理异步操作:
function* fetchUserData() {
const user = yield fetch('/api/user');
const posts = yield fetch(`/api/posts?userId=${user.id}`);
return posts;
}
// 需要配合执行器使用
function run(generator) {
const gen = generator();
function handle(result) {
if (result.done) return result.value;
return result.value.then(data => {
return handle(gen.next(data));
});
}
return handle(gen.next());
}
run(fetchUserData).then(posts => {
console.log('用户文章:', posts);
});生成无限序列
Generator很适合生成无限序列,因为它是按需生成的:
function* fibonacci() {
let a = 0, b = 1;
while (true) {
yield b;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
// 只取前10个斐波那契数
for (let i = 0; i < 10; i++) {
console.log(fib.next().value);
}处理大数据集
当需要处理大量数据时,可以分批处理:
function* batchProcessor(data, batchSize = 100) {
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
yield batch; // 每次返回一批数据
}
}
const bigData = new Array(10000).fill(0).map((_, i) => i);
for (const batch of batchProcessor(bigData, 1000)) {
console.log(`处理批次,大小: ${batch.length}`);
// 处理这一批数据
}状态管理
Generator可以用于管理状态流转:
function* orderStateMachine() {
yield "待支付";
yield "已支付";
yield "已发货";
yield "已完成";
}
const order = orderStateMachine();
// 模拟订单状态变化
console.log(order.next().value); // "待支付"
console.log(order.next().value); // "已支付"
console.log(order.next().value); // "已发货"遍历复杂数据结构
Generator让遍历复杂结构变得简单:
function* traverseTree(node) {
if (!node) return;
yield node.value;
if (node.children) {
for (const child of node.children) {
yield* traverseTree(child);
}
}
}
const tree = {
value: '根节点',
children: [
{
value: '子节点1',
children: [
{ value: '孙节点1' }
]
},
{ value: '子节点2' }
]
};
for (const value of traverseTree(tree)) {
console.log(value); // 依次输出所有节点值
}Generator与async/await的关系
async/await实际上是Generator的语法糖,但它们各有用途:
| 特性 | Generator | async/await |
|---|---|---|
| 语法 | function* + yield | async + await |
| 执行 | 需要手动调用next() | 自动执行 |
| 用途 | 多种场景 | 主要处理异步 |
| 错误处理 | 使用throw()方法 | 使用try/catch |
为什么还要学习Generator?
理解底层原理:明白Generator如何工作,才能更好理解async/await
处理复杂遍历:对于无限序列、树结构遍历,Generator更合适
精确控制执行:在需要手动控制执行节奏的场景中很有用
实用技巧
创建可取消的任务
function* cancellableTask() {
let isCancelled = false;
try {
yield "任务开始";
// 模拟长时间运行的任务
for (let i = 0; i < 100; i++) {
if (isCancelled) {
yield "任务已取消";
return;
}
yield `进度: ${i}%`;
}
yield "任务完成";
} catch (error) {
yield `任务出错: ${error.message}`;
}
return {
cancel: () => { isCancelled = true; }
};
}实现简单的状态机
function* trafficLight() {
while (true) {
yield "绿灯 - 通行";
yield "黄灯 - 注意";
yield "红灯 - 停止";
}
}
const light = trafficLight();
setInterval(() => {
console.log(light.next().value);
}, 3000);总结
Generator为JavaScript带来了可控的执行流程,让函数可以在执行过程中暂停和继续。
虽然async/await在异步编程中更常用,但Generator在以下场景中仍然不可替代:
生成无限序列
遍历复杂数据结构
分批处理大数据
实现状态机
需要精确控制执行流程的场景
学习Generator不仅能解决实际问题,还能加深对JavaScript执行机制的理解。它是从"会用JavaScript"到"懂JavaScript"的重要一步。
本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!