各位观众老爷们,今天咱们来聊聊 JavaScript 里一个挺有意思的东西:Generator,以及怎么用它来实现无限序列,比如说那个著名的斐波那契数列。这玩意儿听起来高大上,其实理解起来也不难,咱们争取用最接地气的方式把它讲明白。
开场白:什么是 Generator?
先别急着斐波那契,咱们得先搞清楚 Generator 是个什么玩意儿。你可以把它想象成一个“暂停按钮”,普通的函数一运行就一口气跑到底,而 Generator 函数可以在运行过程中停下来,等你喊“开始”的时候再继续跑。
这“暂停”和“继续”的关键在于 yield 关键字。yield 就像一个路标,告诉 Generator 函数:到这儿就歇会儿,把后面的值吐出去。
Generator 函数的基本语法
Generator 函数的定义和普通函数差不多,只不过多了一个星号 *。
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const generator = myGenerator(); // 注意!这里并没有执行函数体,而是返回一个 Generator 对象
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 }
咱们来解读一下这段代码:
function* myGenerator(): 定义了一个Generator函数。注意那个星号*,它告诉 JavaScript 引擎,这是一个特殊的函数。const generator = myGenerator();: 调用Generator函数,注意! 这时候函数体并没有执行,而是返回一个Generator对象。这个对象就像一个迭代器,可以控制函数的执行。generator.next(): 调用Generator对象的next()方法,开始或继续执行Generator函数。每次调用next(),函数会执行到下一个yield语句,然后暂停,并返回一个对象,包含两个属性:value:yield后面表达式的值。done: 一个布尔值,表示Generator函数是否执行完毕。false表示还没完,true表示已经结束了。
用 Generator 实现斐波那契数列
现在,咱们进入正题,看看怎么用 Generator 实现斐波那契数列。 斐波那契数列的定义是:第一项和第二项都是 1,从第三项开始,每一项都是前两项的和。 也就是:1, 1, 2, 3, 5, 8, 13, 21, …
function* fibonacci() {
let a = 1;
let b = 1;
while (true) { // 无限循环!
yield a;
[a, b] = [b, a + b]; // ES6 的解构赋值,简洁明了
}
}
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
console.log(fib.next().value); // 8
// ... 可以一直调用下去
这段代码的核心在于 while (true) 循环,这意味着这个 Generator 函数可以无限地生成斐波那契数列的项。
咱们来分析一下:
let a = 1; let b = 1;: 初始化斐波那契数列的前两项。while (true): 创建一个无限循环。yield a;:yield当前的斐波那契数a。[a, b] = [b, a + b];: 用 ES6 的解构赋值更新a和b,计算下一个斐波那契数。 相当于:let temp = b; b = a + b; a = temp;const fib = fibonacci();: 创建一个斐波那契数列的Generator对象。fib.next().value: 调用next()方法,获取下一个斐波那契数。
Generator 的优势:惰性求值
Generator 最大的优势在于惰性求值(Lazy Evaluation)。 也就是说,它不会一次性计算出所有的斐波那契数,而是只有在你调用 next() 方法的时候,才会计算并返回下一个数。
这对于无限序列来说非常重要。 如果不用 Generator,而是直接用数组存储斐波那契数列,那么程序很快就会耗尽内存。
Generator 的应用场景
除了生成无限序列,Generator 还有很多其他的应用场景:
- 异步编程: 可以用
Generator来简化异步操作,配合async/await语法,可以让异步代码看起来像同步代码一样。 - 状态管理: 可以用
Generator来管理复杂的状态,例如游戏的状态机。 - 数据流处理: 可以用
Generator来处理大量的数据流,例如读取文件或网络数据。
Generator 的进阶用法
Generator 还有一些进阶用法,比如:
- *`yield
委托**: 可以将一个Generator函数委托给另一个Generator` 函数。 next()方法传参: 可以给next()方法传递参数,这个参数会作为上一个yield表达式的返回值。return()和throw()方法: 可以提前结束Generator函数,或者抛出一个错误。
咱们先来看 yield* 的用法:
function* anotherGenerator() {
yield 4;
yield 5;
yield 6;
}
function* myGenerator() {
yield 1;
yield 2;
yield 3;
yield* anotherGenerator(); // 委托给 anotherGenerator
yield 7;
yield 8;
}
const generator = myGenerator();
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: false }
console.log(generator.next()); // { value: 5, done: false }
console.log(generator.next()); // { value: 6, done: false }
console.log(generator.next()); // { value: 7, done: false }
console.log(generator.next()); // { value: 8, done: false }
console.log(generator.next()); // { value: undefined, done: true }
yield* anotherGenerator() 会将 anotherGenerator 生成的值依次 yield 出来,就像把 anotherGenerator 的代码直接复制到 myGenerator 里一样。
再来看看 next() 传参的例子:
function* myGenerator() {
const result = yield 'What is your name?';
yield `Hello, ${result}!`;
}
const generator = myGenerator();
console.log(generator.next()); // { value: 'What is your name?', done: false }
console.log(generator.next('Alice')); // { value: 'Hello, Alice!', done: false }
console.log(generator.next()); // { value: undefined, done: true }
第一次调用 next() 时,Generator 函数执行到 yield 'What is your name?',暂停并返回字符串 "What is your name?"。
第二次调用 next('Alice') 时,字符串 "Alice" 会作为上一个 yield 表达式 yield 'What is your name?' 的返回值,赋值给变量 result。 然后,Generator 函数继续执行,yield Hello, ${result}!“,返回字符串 "Hello, Alice!"。
最后,return() 和 throw() 方法分别用于提前结束 Generator 函数和抛出错误。
function* myGenerator() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log('Cleaning up...');
}
}
const generator = myGenerator();
console.log(generator.next()); // { value: 1, done: false }
console.log(generator.return('Finished')); // { value: 'Finished', done: true }
// Cleaning up...
function* myGenerator2() {
try {
yield 1;
yield 2;
yield 3;
} finally {
console.log('Cleaning up...');
}
}
const generator2 = myGenerator2();
console.log(generator2.next()); // { value: 1, done: false }
try {
generator2.throw(new Error('Something went wrong!'));
} catch (e) {
console.error(e); // Error: Something went wrong!
}
// Cleaning up...
return() 方法会立即结束 Generator 函数,并返回一个 done 为 true 的对象。 finally 块中的代码会被执行。
throw() 方法会抛出一个错误,可以在 try...catch 块中捕获。 finally 块中的代码也会被执行。
总结
咱们今天主要讲了 Generator 函数的基本概念和用法,以及如何用它来实现无限序列,例如斐波那契数列。 Generator 的核心在于 yield 关键字,它可以让函数在执行过程中暂停和继续。 Generator 的优势在于惰性求值,可以避免一次性计算大量数据,节省内存。
Generator 在异步编程、状态管理、数据流处理等方面都有广泛的应用。 yield*、next() 传参、return() 和 throw() 方法可以让我们更灵活地控制 Generator 函数的执行。
希望今天的讲解对大家有所帮助。 如果有什么问题,欢迎提问。 祝大家编程愉快!