Appearance
JavaScript 生成器
什么是生成器
生成器(Generator)是 ES6(ECMAScript 2015)引入的一种特殊函数,它可以暂停执行并在稍后恢复执行。生成器函数使用 function* 语法定义,返回一个生成器对象,该对象实现了迭代器协议。
生成器的主要特点:
- 可以暂停执行(使用
yield语句) - 可以恢复执行(使用
next()方法) - 可以向生成器传递值(通过
next()方法的参数) - 可以抛出异常(使用
throw()方法) - 实现了迭代器协议,可以在
for...of循环中使用
生成器函数的定义
1. 基本语法
使用 function* 关键字定义生成器函数。
javascript
// 示例
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}
// 创建生成器对象
const generator = generatorFunction();
// 使用生成器
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 }2. 生成器函数表达式
也可以使用函数表达式的形式定义生成器函数。
javascript
// 示例
const generatorFunction = function* () {
yield 1;
yield 2;
yield 3;
};
const generator = generatorFunction();
console.log(generator.next()); // { value: 1, done: false }3. 生成器方法
在对象字面量和类中,可以定义生成器方法。
javascript
// 示例
// 对象字面量中的生成器方法
const obj = {
*generatorMethod() {
yield 1;
yield 2;
},
};
const generator1 = obj.generatorMethod();
console.log(generator1.next()); // { value: 1, done: false }
// 类中的生成器方法
class MyClass {
*generatorMethod() {
yield 1;
yield 2;
}
}
const instance = new MyClass();
const generator2 = instance.generatorMethod();
console.log(generator2.next()); // { value: 1, done: false }生成器的工作原理
1. yield 语句
yield 语句用于暂停生成器的执行,并返回一个包含 value 和 done 属性的对象。
value:yield 表达式的值done:布尔值,表示生成器是否已完成执行
javascript
// 示例
function* generatorFunction() {
console.log("开始执行");
yield "第一个值";
console.log("继续执行");
yield "第二个值";
console.log("执行完成");
}
const generator = generatorFunction();
console.log("创建生成器后");
console.log(generator.next()); // 开始执行,{ value: '第一个值', done: false }
console.log("第一次 next() 后");
console.log(generator.next()); // 继续执行,{ value: '第二个值', done: false }
console.log("第二次 next() 后");
console.log(generator.next()); // 执行完成,{ value: undefined, done: true }2. next() 方法
next() 方法用于恢复生成器的执行,返回下一个 yield 表达式的值。
- 第一次调用
next()时,生成器开始执行,直到遇到第一个yield语句 - 后续调用
next()时,生成器从上次暂停的地方继续执行,直到遇到下一个yield语句或函数结束
javascript
// 示例
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
return 4; // return 语句的值会作为最后一次 next() 的 value
}
const generator = generatorFunction();
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: 4, done: true } // 注意:done 为 true3. 向生成器传递值
可以通过 next() 方法的参数向生成器传递值,这个值会作为上一个 yield 表达式的返回值。
javascript
// 示例
function* generatorFunction() {
const value1 = yield "请输入第一个值";
console.log("收到第一个值:", value1);
const value2 = yield "请输入第二个值";
console.log("收到第二个值:", value2);
return `两个值的和: ${value1 + value2}`;
}
const generator = generatorFunction();
console.log(generator.next()); // { value: '请输入第一个值', done: false }
console.log(generator.next(10)); // 收到第一个值: 10, { value: '请输入第二个值', done: false }
console.log(generator.next(20)); // 收到第二个值: 20, { value: '两个值的和: 30', done: true }4. 抛出异常
可以使用 throw() 方法向生成器抛出异常,异常会在生成器暂停的地方被抛出。
javascript
// 示例
function* generatorFunction() {
try {
yield "正常执行";
console.log("这里不会执行");
} catch (error) {
console.log("捕获到异常:", error.message);
yield "从异常中恢复";
}
}
const generator = generatorFunction();
console.log(generator.next()); // { value: '正常执行', done: false }
console.log(generator.throw(new Error("出错了"))); // 捕获到异常: 出错了, { value: '从异常中恢复', done: false }
console.log(generator.next()); // { value: undefined, done: true }5. 生成器的返回值
生成器函数可以使用 return 语句返回值,这个值会作为最后一次 next() 调用的 value,但只有在 done 为 true 时才会返回。
javascript
// 示例
function* generatorFunction() {
yield 1;
yield 2;
return 3;
}
const generator = generatorFunction();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.next()); // { value: 2, done: false }
console.log(generator.next()); // { value: 3, done: true }
// 注意:在 for...of 循环中,return 的值不会被迭代
for (const value of generatorFunction()) {
console.log(value); // 1, 2(不会输出 3)
}生成器的应用场景
1. 迭代器
生成器实现了迭代器协议,可以轻松创建迭代器。
javascript
// 示例
// 创建一个范围迭代器
function* range(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
// 使用 for...of 循环
for (const num of range(1, 5)) {
console.log(num); // 1, 2, 3, 4, 5
}
// 转换为数组
const numbers = [...range(1, 10, 2)];
console.log(numbers); // [1, 3, 5, 7, 9]
// 使用解构赋值
const [first, second, ...rest] = range(1, 5);
console.log(first, second, rest); // 1 2 [3, 4, 5]2. 异步编程
生成器可以用于简化异步编程,结合 yield 和 Promise 可以实现类似 async/await 的语法。
javascript
// 示例
// 模拟异步操作
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(`数据 from ${url}`);
}, 1000);
});
}
// 生成器函数
function* asyncGenerator() {
console.log("开始");
const data1 = yield fetchData("url1");
console.log(data1);
const data2 = yield fetchData("url2");
console.log(data2);
return "完成";
}
// 手动执行生成器
const generator = asyncGenerator();
function runGenerator(generator) {
function process(result) {
if (result.done) {
console.log("最终结果:", result.value);
return;
}
result.value
.then((data) => {
process(generator.next(data));
})
.catch((error) => {
generator.throw(error);
});
}
process(generator.next());
}
runGenerator(generator);
// 输出:
// 开始
// 数据 from url1
// 数据 from url2
// 最终结果: 完成3. 无限序列
生成器可以用于创建无限序列,因为它们只在需要时生成值。
javascript
// 示例
// 生成无限的斐波那契数列
function* fibonacci() {
let a = 0,
b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 使用
const fib = fibonacci();
console.log(fib.next().value); // 0
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
// 生成无限的递增序列
function* increment(start = 0, step = 1) {
let current = start;
while (true) {
yield current;
current += step;
}
}
const counter = increment(10, 5);
console.log(counter.next().value); // 10
console.log(counter.next().value); // 15
console.log(counter.next().value); // 204. 状态管理
生成器可以用于管理状态,因为它们可以在执行过程中保持状态。
javascript
// 示例
// 简单的状态机
function* stateMachine() {
let state = "idle";
while (true) {
const action = yield state;
switch (action) {
case "start":
state = "running";
break;
case "pause":
state = "paused";
break;
case "stop":
state = "stopped";
break;
default:
state = "idle";
}
}
}
const machine = stateMachine();
console.log(machine.next().value); // 'idle'
console.log(machine.next("start").value); // 'running'
console.log(machine.next("pause").value); // 'paused'
console.log(machine.next("stop").value); // 'stopped'
console.log(machine.next("reset").value); // 'idle'5. 惰性计算
生成器可以用于实现惰性计算,只在需要时才计算值。
javascript
// 示例
// 惰性计算平方数
function* squaredNumbers(numbers) {
for (const num of numbers) {
console.log(`计算 ${num} 的平方`);
yield num * num;
}
}
// 使用
const numbers = [1, 2, 3, 4, 5];
const squared = squaredNumbers(numbers);
// 只计算需要的值
console.log("取第一个值:");
console.log(squared.next().value); // 计算 1 的平方, 1
console.log("取第二个值:");
console.log(squared.next().value); // 计算 2 的平方, 4
// 剩余的值不会计算,除非显式请求6. 协程
生成器可以用于实现协程,允许多个任务交替执行。
javascript
// 示例
// 协程示例
function* task1() {
for (let i = 1; i <= 3; i++) {
console.log(`任务1: 步骤 ${i}`);
yield;
}
}
function* task2() {
for (let i = 1; i <= 3; i++) {
console.log(`任务2: 步骤 ${i}`);
yield;
}
}
// 调度器
function scheduler(tasks) {
const generators = tasks.map((task) => task());
let current = 0;
while (generators.some((gen) => !gen.next().done)) {
current = (current + 1) % generators.length;
}
}
// 执行
console.log("开始执行协程");
scheduler([task1, task2]);
// 输出:
// 任务1: 步骤 1
// 任务2: 步骤 1
// 任务1: 步骤 2
// 任务2: 步骤 2
// 任务1: 步骤 3
// 任务2: 步骤 3生成器的方法
1. next(value)
恢复生成器的执行,返回下一个 yield 表达式的值。
- 参数:
value- 作为上一个yield表达式的返回值 - 返回值:包含
value和done属性的对象
javascript
// 示例
function* generatorFunction() {
const value = yield "请输入值";
return `你输入了: ${value}`;
}
const generator = generatorFunction();
console.log(generator.next()); // { value: '请输入值', done: false }
console.log(generator.next("Hello")); // { value: '你输入了: Hello', done: true }2. throw(error)
向生成器抛出异常,异常会在生成器暂停的地方被抛出。
- 参数:
error- 要抛出的异常对象 - 返回值:与
next()相同,返回下一个yield表达式的值
javascript
// 示例
function* generatorFunction() {
try {
yield "正常执行";
} catch (error) {
console.log("捕获到异常:", error.message);
yield "从异常中恢复";
}
}
const generator = generatorFunction();
console.log(generator.next()); // { value: '正常执行', done: false }
console.log(generator.throw(new Error("出错了"))); // 捕获到异常: 出错了, { value: '从异常中恢复', done: false }
console.log(generator.next()); // { value: undefined, done: true }3. return(value)
终止生成器的执行,返回指定的值。
- 参数:
value- 作为生成器的返回值 - 返回值:包含
value和done: true的对象
javascript
// 示例
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}
const generator = generatorFunction();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.return("结束")); // { value: '结束', done: true }
console.log(generator.next()); // { value: undefined, done: true }生成器与迭代器的关系
1. 生成器实现了迭代器协议
生成器对象实现了迭代器协议,因此可以在任何需要迭代器的地方使用。
javascript
// 示例
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}
const generator = generatorFunction();
// 使用迭代器协议
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 }
// 在 for...of 循环中使用
for (const value of generatorFunction()) {
console.log(value); // 1, 2, 3
}
// 在扩展语法中使用
const array = [...generatorFunction()];
console.log(array); // [1, 2, 3]
// 在解构赋值中使用
const [a, b, c] = generatorFunction();
console.log(a, b, c); // 1, 2, 32. 自定义迭代器 vs 生成器
生成器提供了一种更简洁的方式来创建迭代器,相比自定义迭代器,代码更易读。
javascript
// 示例
// 自定义迭代器
const customIterator = {
start: 1,
end: 3,
[Symbol.iterator]() {
let current = this.start;
return {
next: () => {
if (current <= this.end) {
return { value: current++, done: false };
} else {
return { value: undefined, done: true };
}
},
};
},
};
// 使用自定义迭代器
for (const value of customIterator) {
console.log(value); // 1, 2, 3
}
// 使用生成器创建迭代器
function* generatorIterator(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
// 使用生成器迭代器
for (const value of generatorIterator(1, 3)) {
console.log(value); // 1, 2, 3
}生成器的注意事项
1. 生成器函数的 this
生成器函数的 this 绑定与普通函数不同,需要特别注意。
javascript
// 示例
function* generatorFunction() {
yield this.name;
}
const obj = {
name: "John",
generator: generatorFunction,
};
// 直接调用,this 指向全局对象
const generator1 = generatorFunction();
console.log(generator1.next().value); // undefined(在浏览器中)
// 作为对象方法调用,this 指向对象
const generator2 = obj.generator();
console.log(generator2.next().value); // 'John'
// 使用 bind 绑定 this
const boundGenerator = generatorFunction.bind({ name: "Jane" });
const generator3 = boundGenerator();
console.log(generator3.next().value); // 'Jane'2. 生成器的状态
生成器对象是有状态的,一旦迭代完成,就不能重新开始。
javascript
// 示例
function* generatorFunction() {
yield 1;
yield 2;
yield 3;
}
const generator = generatorFunction();
// 迭代完成
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 }
// 不能重新开始,会一直返回 done: true
console.log(generator.next()); // { value: undefined, done: true }
// 需要创建新的生成器对象
const newGenerator = generatorFunction();
console.log(newGenerator.next()); // { value: 1, done: false }3. 生成器的性能
生成器的性能比普通函数稍差,因为它们需要维护执行状态。在性能敏感的场景中,应该谨慎使用。
javascript
// 示例
// 性能测试
function* generatorFunction(n) {
for (let i = 0; i < n; i++) {
yield i;
}
}
function regularFunction(n) {
const result = [];
for (let i = 0; i < n; i++) {
result.push(i);
}
return result;
}
// 测试生成器性能
const n = 1000000;
console.time("generator");
const generator = generatorFunction(n);
for (const value of generator) {
// 迭代
}
console.timeEnd("generator");
// 测试普通函数性能
console.time("regular");
const array = regularFunction(n);
for (const value of array) {
// 迭代
}
console.timeEnd("regular");
// 结果:普通函数通常比生成器快4. 生成器与箭头函数
箭头函数不能作为生成器函数,因为箭头函数没有自己的 this 绑定,也不能使用 function* 语法。
javascript
// 示例
// 错误:箭头函数不能作为生成器函数
// const generatorFunction = *() => {
// yield 1;
// };
// 正确:使用普通函数
const generatorFunction = function* () {
yield 1;
};生成器的最佳实践
1. 合理使用生成器
生成器适用于以下场景:
- 需要惰性计算的场景
- 需要无限序列的场景
- 需要状态管理的场景
- 需要简化异步编程的场景
对于简单的同步操作,普通函数通常更高效。
2. 结合 Promise 使用
生成器与 Promise 结合使用,可以实现类似 async/await 的语法,使异步代码更易读。
javascript
// 示例
// 模拟异步操作
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// 生成器函数
function* asyncTask() {
console.log("开始");
yield delay(1000);
console.log("1秒后");
yield delay(1000);
console.log("2秒后");
return "完成";
}
// 执行生成器
function run(generator) {
return new Promise((resolve, reject) => {
function process(result) {
if (result.done) {
resolve(result.value);
return;
}
result.value
.then((value) => process(generator.next(value)))
.catch((error) => generator.throw(error));
}
try {
process(generator.next());
} catch (error) {
reject(error);
}
});
}
// 使用
run(asyncTask()).then((result) => {
console.log(result); // '完成'
});3. 使用生成器创建迭代器
生成器是创建迭代器的简便方法,特别是对于复杂的迭代逻辑。
javascript
// 示例
// 生成器创建迭代器
function* createIterator(data) {
// 复杂的迭代逻辑
for (let i = 0; i < data.length; i++) {
if (data[i] % 2 === 0) {
yield data[i];
}
}
}
// 使用
const data = [1, 2, 3, 4, 5, 6];
for (const value of createIterator(data)) {
console.log(value); // 2, 4, 6
}4. 错误处理
在生成器中,应该使用 try-catch 捕获异常,确保生成器可以从错误中恢复。
javascript
// 示例
function* generatorFunction() {
try {
// 可能出错的代码
const result = yield someAsyncOperation();
console.log(result);
} catch (error) {
// 错误处理
console.error("错误:", error);
// 可以选择从错误中恢复
yield "错误已处理";
}
}
const generator = generatorFunction();
generator.next();
generator.throw(new Error("操作失败"));生成器的兼容性
1. 浏览器兼容性
| 浏览器 | 版本 | 支持情况 |
|---|---|---|
| Chrome | 39+ | 支持 |
| Firefox | 26+ | 支持 |
| Safari | 10+ | 支持 |
| Edge | 13+ | 支持 |
| IE | 所有版本 | 不支持 |
2. Node.js 兼容性
| Node.js 版本 | 支持情况 |
|---|---|
| 4.0+ | 支持 |
3. 转译工具
对于不支持生成器的环境,可以使用 Babel 等转译工具进行转译。
javascript
// Babel 配置示例 (.babelrc)
{
"presets": ["@babel/preset-env"]
}总结
生成器是 JavaScript 中强大的特性,它允许函数暂停执行并在稍后恢复执行。生成器的主要特点包括:
- 暂停和恢复:使用
yield语句暂停执行,使用next()方法恢复执行 - 值传递:可以通过
next()方法的参数向生成器传递值 - 异常处理:可以使用
throw()方法向生成器抛出异常 - 迭代器协议:生成器对象实现了迭代器协议,可以在
for...of循环中使用 - 惰性计算:只在需要时生成值,适合处理大型数据集或无限序列
生成器的应用场景非常广泛,包括:
- 创建迭代器
- 简化异步编程
- 生成无限序列
- 管理状态
- 实现惰性计算
- 实现协程
虽然生成器的性能比普通函数稍差,但在适当的场景中使用,它们可以使代码更易读、更易维护。在现代 JavaScript 中,async/await 语法在很多情况下已经替代了生成器用于异步编程,但生成器仍然是处理迭代和状态管理的强大工具。
通过理解和掌握生成器,我们可以编写更加灵活、高效的 JavaScript 代码。