好嘞!各位观众老爷们,今天咱们就来聊聊 JavaScript 中那些神出鬼没的 Generator
函数,看看它们是如何化身成为迭代器工厂,打造属于我们自己的迭代器军团的!
开场白:迭代器是个啥玩意儿?
在正式开讲 Generator
之前,咱们先来回顾一下迭代器是个啥。简单来说,迭代器就是个“遍历器”,能让你逐个访问集合中的元素,而不用操心底层的数据结构。想象一下,你有一串葡萄,迭代器就像是那个帮你一颗一颗摘葡萄的小助手。
在 JavaScript 中,迭代器是一个对象,它必须包含一个 next()
方法。这个 next()
方法会返回一个对象,包含两个属性:
value
:当前迭代到的值。done
:一个布尔值,表示迭代是否结束。true
表示迭代完成,false
表示还有更多元素。
举个栗子:
const myArray = [1, 2, 3];
// 手动创建一个迭代器
const myIterator = {
index: 0,
next: function() {
if (this.index < myArray.length) {
return { value: myArray[this.index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }
这段代码创建了一个手动迭代器,它可以遍历数组 myArray
。虽然手动创建迭代器能让你更了解迭代器的本质,但写起来实在太麻烦了!这时候,Generator
函数就该闪亮登场了。
主角登场:Generator
函数的魔法
Generator
函数是一种特殊的函数,它使用 function*
语法定义,并且可以在函数体内使用 yield
关键字。yield
就像一个暂停按钮,可以让函数暂停执行,并将 yield
后面的值作为迭代器的 value
返回。当你再次调用迭代器的 next()
方法时,函数会从上次暂停的地方继续执行。
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const iterator = myGenerator();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
看到了吗?使用 Generator
函数,我们只需要几行代码就能创建一个迭代器,简直不要太方便!
Generator
函数的特性总结:
特性 | 描述 |
---|---|
语法 | 使用 function* 定义。 |
yield 关键字 |
暂停函数执行,并将 yield 后面的值作为迭代器的 value 返回。 |
迭代器工厂 | 调用 Generator 函数会返回一个迭代器对象。 |
next() 方法 |
用于继续执行 Generator 函数,并获取下一个 yield 的值。 |
状态保持 | Generator 函数会记住上次 yield 的状态,下次调用 next() 方法时会从上次暂停的地方继续执行。 |
实战演练:创建自定义迭代器
接下来,咱们来几个实战例子,看看如何使用 Generator
函数创建各种各样的自定义迭代器。
1. 创建一个无限迭代器:斐波那契数列
斐波那契数列是一个经典的数列,它的每一项都是前两项的和。我们可以使用 Generator
函数创建一个无限迭代器,不断生成斐波那契数列的下一项。
function* fibonacci() {
let a = 0;
let 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
// ... 无限延续
这个例子展示了 Generator
函数的强大之处,它可以轻松创建无限序列的迭代器。
2. 遍历对象属性:一个更优雅的方式
以前,我们遍历对象属性通常使用 for...in
循环。但是,for...in
循环会遍历对象原型链上的属性,这有时候并不是我们想要的。使用 Generator
函数,我们可以创建一个只遍历对象自身属性的迭代器。
function* objectEntries(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
yield [key, obj[key]];
}
}
}
const myObj = { a: 1, b: 2, c: 3 };
for (let [key, value] of objectEntries(myObj)) {
console.log(`${key}: ${value}`);
}
// 输出:
// a: 1
// b: 2
// c: 3
在这个例子中,objectEntries
函数返回一个迭代器,它可以遍历对象 myObj
的所有自身属性。使用 for...of
循环,我们可以轻松地访问每个属性的键和值。
3. 创建一个范围迭代器:指定起始值和结束值
有时候,我们需要一个可以生成指定范围内的数字的迭代器。Generator
函数可以轻松实现这个功能。
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
const myRange = range(1, 5);
console.log(myRange.next().value); // 1
console.log(myRange.next().value); // 2
console.log(myRange.next().value); // 3
console.log(myRange.next().value); // 4
console.log(myRange.next().value); // 5
console.log(myRange.next().value); // undefined
这个例子创建了一个 range
函数,它可以生成从 start
到 end
的所有整数。
4. 处理异步操作:让异步代码更优雅
Generator
函数还可以与 Promise
结合使用,让异步代码更加优雅。通过 yield
一个 Promise
对象,我们可以暂停 Generator
函数的执行,直到 Promise
对象 resolve。
function fetchData(url) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
function* myAsyncGenerator() {
const data1 = yield fetchData('url1');
console.log(data1);
const data2 = yield fetchData('url2');
console.log(data2);
}
function run(generator) {
const iterator = generator();
function handleNext(value) {
const next = iterator.next(value);
if (next.done) {
return;
}
if (next.value instanceof Promise) {
next.value.then(handleNext);
} else {
handleNext(next.value);
}
}
handleNext();
}
run(myAsyncGenerator);
// 输出:
// Data from url1
// Data from url2
在这个例子中,myAsyncGenerator
函数使用 yield
关键字暂停执行,等待 fetchData
函数返回的 Promise
对象 resolve。run
函数负责驱动 Generator
函数的执行,并处理 Promise
对象。
5. 更复杂的例子:树的深度优先遍历
假设我们有一个树结构,我们想要使用迭代器来实现树的深度优先遍历。
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
addChild(node) {
this.children.push(node);
return this;
}
}
function* depthFirstTraversal(root) {
yield root.value;
for (let child of root.children) {
yield* depthFirstTraversal(child); // 使用 yield* 递归调用
}
}
const root = new TreeNode('A');
root.addChild(new TreeNode('B'))
.addChild(new TreeNode('C')
.addChild(new TreeNode('D'))
.addChild(new TreeNode('E')));
root.addChild(new TreeNode('F'));
for (let value of depthFirstTraversal(root)) {
console.log(value);
}
// 输出:
// A
// B
// C
// D
// E
// F
这个例子展示了如何使用 yield*
关键字来委托给另一个迭代器。yield* depthFirstTraversal(child)
会将 child
树的深度优先遍历委托给另一个 depthFirstTraversal
函数,从而实现整个树的深度优先遍历。
yield*
的妙用
在上面的例子中,我们看到了 yield*
关键字的用法。yield*
允许我们将一个迭代器委托给另一个迭代器。它的作用是将另一个迭代器产生的所有值都 yield 出来,就像将另一个迭代器的代码直接插入到当前迭代器中一样。
*`yield` 的作用:**
- 委托迭代: 将一个迭代器的控制权交给另一个迭代器。
- 代码复用: 避免重复编写迭代逻辑。
- 递归迭代: 在递归函数中使用,可以方便地遍历树形结构等复杂数据结构。
总结:Generator
函数,迭代器的瑞士军刀
总而言之,Generator
函数是 JavaScript 中一个非常强大的工具,它可以帮助我们轻松创建各种各样的自定义迭代器。无论是简单的范围迭代器,还是复杂的异步迭代器,Generator
函数都能胜任。掌握了 Generator
函数,你就能像拥有了一把迭代器的瑞士军刀,在处理集合数据时游刃有余。
希望今天的讲座能帮助大家更好地理解 Generator
函数,并在实际开发中灵活运用。下次再见!