各位观众老爷们,大家好!我是今天的讲师,咱们今天聊聊 JavaScript 中相当酷炫的 Generator
和 yield*
,特别是它们在委托迭代中的妙用。保证让你听完之后,感觉自己立马升华,代码功力大增!
一、Generator:暂停的艺术
首先,咱们得搞清楚啥是 Generator
。简单来说,Generator
是一种特殊的函数,它可以“暂停”执行,并在稍后“恢复”执行。听起来有点像科幻片里的时间暂停器,对吧?
普通的函数,一旦执行,就像脱缰的野马,一路狂奔到结尾,除非遇到 return
才会停下来。而 Generator
函数,就像一位武林高手,可以在关键时刻“闭关修炼”,下次需要的时候再“出关”。
定义 Generator
函数,需要在 function
关键字后面加个 *
。然后,在函数体内使用 yield
关键字来暂停函数的执行,并返回一个值。
function* myGenerator() {
console.log("开始执行...");
yield 1;
console.log("暂停后恢复执行...");
yield 2;
console.log("又暂停了...");
yield 3;
console.log("执行完毕!");
}
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: undefined, done: true }
在这个例子中,myGenerator
函数就是一个 Generator
。每次调用 generator.next()
,函数就会执行到下一个 yield
语句,然后暂停,并返回一个对象,这个对象有两个属性:
value
:yield
语句后面的值。done
: 一个布尔值,表示Generator
是否执行完毕。
当 done
为 true
时,表示 Generator
已经执行完毕,再调用 next()
就只会返回 { value: undefined, done: true }
。
*二、`yield`:代理人模式的迭代器**
现在,咱们来说说 yield*
。这玩意儿有点像一个“代理人”,它可以将迭代的任务“委托”给另一个迭代器或者 Generator
。
yield*
后面可以跟任何可迭代对象(iterable),比如数组、字符串、Map
、Set
,甚至其他的 Generator
。它会将可迭代对象中的每一个值依次 yield
出去。
function* anotherGenerator() {
yield "a";
yield "b";
yield "c";
}
function* mainGenerator() {
yield 1;
yield* anotherGenerator(); // 委托给 anotherGenerator
yield 2;
}
const main = mainGenerator();
console.log(main.next()); // { value: 1, done: false }
console.log(main.next()); // { value: 'a', done: false }
console.log(main.next()); // { value: 'b', done: false }
console.log(main.next()); // { value: 'c', done: false }
console.log(main.next()); // { value: 2, done: false }
console.log(main.next()); // { value: undefined, done: true }
在这个例子中,mainGenerator
函数使用 yield* anotherGenerator()
将迭代的任务委托给了 anotherGenerator
。 mainGenerator
会先 yield 1
,然后会依次 yield
出 anotherGenerator
中的 "a", "b", "c",最后再 yield 2
。
*三、`yield` 的应用场景:委托迭代**
yield*
最常用的场景就是委托迭代。想象一下,你有一个复杂的嵌套数据结构,比如一个包含多个数组的数组,或者一个树形结构。如果你想迭代这个数据结构中的所有元素,用普通的 for
循环可能会写得非常冗长和复杂。
但是,如果你使用 Generator
和 yield*
,就可以非常优雅地实现这个功能。
场景一:扁平化嵌套数组
假设你有一个嵌套的数组:
const nestedArray = [1, [2, [3, 4], 5], 6];
你想把它扁平化成一个一维数组:[1, 2, 3, 4, 5, 6]
。
function* flatten(arr) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (Array.isArray(item)) {
yield* flatten(item); // 递归调用 flatten,并委托迭代
} else {
yield item;
}
}
}
const flattenedArray = [...flatten(nestedArray)]; // 使用扩展运算符将 Generator 转换为数组
console.log(flattenedArray); // [1, 2, 3, 4, 5, 6]
在这个例子中,flatten
函数是一个 Generator
。它遍历数组中的每一个元素。如果元素是数组,就递归调用 flatten
函数,并使用 yield*
将迭代的任务委托给递归调用的 flatten
函数。如果元素不是数组,就直接 yield
这个元素。
场景二:遍历树形结构
假设你有一个树形结构:
const tree = {
value: 1,
children: [
{ value: 2, children: [{ value: 4 }, { value: 5 }] },
{ value: 3, children: [{ value: 6 }] },
],
};
你想遍历这棵树的所有节点,并按顺序访问每个节点的值。
function* traverseTree(node) {
yield node.value;
if (node.children) {
for (const child of node.children) {
yield* traverseTree(child); // 递归调用 traverseTree,并委托迭代
}
}
}
const treeValues = [...traverseTree(tree)];
console.log(treeValues); // [1, 2, 4, 5, 3, 6]
在这个例子中,traverseTree
函数也是一个 Generator
。它首先 yield
当前节点的值。如果当前节点有子节点,就遍历子节点,并使用 yield*
将迭代的任务委托给递归调用的 traverseTree
函数。
*四、`yield` 的返回值**
yield*
除了可以委托迭代,还可以接收被委托的迭代器的返回值。这个返回值是被委托的迭代器 return
语句返回的值。
function* subGenerator() {
yield 1;
yield 2;
return "Sub Generator Done!"; // 返回值
}
function* mainGenerator() {
const result = yield* subGenerator();
console.log("Sub Generator 返回值:", result);
yield 3;
}
const main = mainGenerator();
console.log(main.next()); // { value: 1, done: false }
console.log(main.next()); // { value: 2, done: false }
console.log(main.next()); // Sub Generator 返回值: Sub Generator Done!
// { value: 3, done: false }
console.log(main.next()); // { value: undefined, done: true }
在这个例子中,subGenerator
函数 return
了一个字符串 "Sub Generator Done!"。mainGenerator
函数使用 yield* subGenerator()
委托迭代,并将 subGenerator
函数的返回值赋值给 result
变量。
*五、`yield与
for…of` 的比较**
你可能会觉得 yield*
和 for...of
循环很像,它们都可以用来迭代可迭代对象。但是,它们之间还是有一些区别的。
特性 | yield* |
for...of |
---|---|---|
作用 | 委托迭代,将可迭代对象中的值 yield 出去 |
迭代可迭代对象,执行循环体中的代码 |
使用场景 | Generator 函数中,用于委托给另一个迭代器或 Generator |
任何需要迭代可迭代对象的场景 |
返回值 | 可以接收被委托的迭代器的返回值 | 没有返回值 |
控制权 | 将迭代的控制权交给被委托的迭代器,直到被委托的迭代器执行完毕 | 循环体完全控制迭代过程 |
暂停和恢复 | 可以暂停和恢复执行,与 Generator 的特性结合 |
无法暂停和恢复执行 |
总的来说,for...of
循环更适合用于简单的迭代,而 yield*
更适合用于复杂的迭代场景,特别是需要委托迭代或者需要接收被委托的迭代器返回值的时候。
*六、Generator
和 `yield` 的注意事项**
Generator
函数必须使用function*
声明。yield
关键字只能在Generator
函数中使用。yield*
后面必须跟一个可迭代对象。Generator
函数返回一个Generator
对象,而不是一个普通的值。- 需要使用
next()
方法来驱动Generator
的执行。 - 可以使用扩展运算符
...
将Generator
转换为数组。
七、总结
今天咱们深入探讨了 JavaScript 中 Generator
和 yield*
的用法,特别是它们在委托迭代中的应用。
Generator
是一种可以暂停和恢复执行的特殊函数。yield
关键字用于暂停Generator
的执行,并返回一个值。yield*
关键字用于委托迭代,将迭代的任务交给另一个迭代器或Generator
。yield*
可以接收被委托的迭代器的返回值。
掌握了 Generator
和 yield*
,可以让你写出更加优雅和高效的代码,特别是在处理复杂的迭代场景时。希望今天的讲座能帮助大家更好地理解和使用这两个强大的特性。
各位,下课!