各位观众老爷们,今天咱们来聊聊 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
函数的执行。
希望今天的讲解对大家有所帮助。 如果有什么问题,欢迎提问。 祝大家编程愉快!