JavaScript内核与高级编程之:`JavaScript` 的 `Generator` 与 `yield*`:其在委托迭代中的应用。

各位观众老爷们,大家好!我是今天的讲师,咱们今天聊聊 JavaScript 中相当酷炫的 Generatoryield*,特别是它们在委托迭代中的妙用。保证让你听完之后,感觉自己立马升华,代码功力大增!

一、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 是否执行完毕。

donetrue 时,表示 Generator 已经执行完毕,再调用 next() 就只会返回 { value: undefined, done: true }

*二、`yield`:代理人模式的迭代器**

现在,咱们来说说 yield*。这玩意儿有点像一个“代理人”,它可以将迭代的任务“委托”给另一个迭代器或者 Generator

yield* 后面可以跟任何可迭代对象(iterable),比如数组、字符串、MapSet,甚至其他的 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() 将迭代的任务委托给了 anotherGeneratormainGenerator 会先 yield 1,然后会依次 yieldanotherGenerator 中的 "a", "b", "c",最后再 yield 2

*三、`yield` 的应用场景:委托迭代**

yield* 最常用的场景就是委托迭代。想象一下,你有一个复杂的嵌套数据结构,比如一个包含多个数组的数组,或者一个树形结构。如果你想迭代这个数据结构中的所有元素,用普通的 for 循环可能会写得非常冗长和复杂。

但是,如果你使用 Generatoryield*,就可以非常优雅地实现这个功能。

场景一:扁平化嵌套数组

假设你有一个嵌套的数组:

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 变量。

*五、`yieldfor…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 中 Generatoryield* 的用法,特别是它们在委托迭代中的应用。

  • Generator 是一种可以暂停和恢复执行的特殊函数。
  • yield 关键字用于暂停 Generator 的执行,并返回一个值。
  • yield* 关键字用于委托迭代,将迭代的任务交给另一个迭代器或 Generator
  • yield* 可以接收被委托的迭代器的返回值。

掌握了 Generatoryield*,可以让你写出更加优雅和高效的代码,特别是在处理复杂的迭代场景时。希望今天的讲座能帮助大家更好地理解和使用这两个强大的特性。

各位,下课!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注