多多读书
1060 字
5 分钟
ES6 yield关键字原来还可以这样用?

你是否曾经感到JavaScript中的异步编程让人头疼?或者想要创建一个可以按需生成值的函数?如果是,那么ES6中的yield关键字可能正是你需要的!让我们深入探讨yield的用法,并通过与普通函数的对比来突出它的优势。

什么是yield?#

简单来说,yield是一个特殊的关键字,用在生成器函数中。它的作用是暂停函数的执行,并返回一个值。当函数被再次调用时,它会从上次暂停的地方继续执行。

示例1: 创建一个简单的计数器#

使用yield的生成器函数:#

function* counterWithYield() {
  let count = 0;
  while (true) {
    yield count++;
  }
}

const genCounter = counterWithYield();
console.log(genCounter.next().value); // 输出: 0
console.log(genCounter.next().value); // 输出: 1
console.log(genCounter.next().value); // 输出: 2

对比:普通函数实现:#

function counterWithoutYield() {
  let count = 0;
  return function() {
    return count++;
  };
}

const normalCounter = counterWithoutYield();
console.log(normalCounter()); // 输出: 0
console.log(normalCounter()); // 输出: 1
console.log(normalCounter()); // 输出: 2

优势对比:

  1. 使用yield的版本可以直接作为迭代器使用,更加灵活。
  2. 生成器函数的状态管理更加直观,不需要闭包。

示例2: 实现斐波那契数列#

使用yield的生成器函数:#

function* fibonacciWithYield() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacciWithYield();
for (let i = 0; i < 10; i++) {
  console.log(fib.next().value);
}
// 输出: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55

对比:普通函数实现:#

function fibonacciWithoutYield(n) {
  const result = [1, 1];
  for (let i = 2; i < n; i++) {
    result[i] = result[i-1] + result[i-2];
  }
  return result;
}

console.log(fibonacciWithoutYield(10));
// 输出: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

优势对比:

  1. 使用yield的版本可以生成无限序列,而不会耗尽内存。
  2. 只在需要时才计算下一个值,实现了惰性计算。
  3. 可以轻松地获取任意数量的斐波那契数,而不需要预先指定。

示例3: 异步流程控制#

使用yield的生成器函数:#

function* fetchUserDataWithYield(userId) {
  const user = yield fetch(`https://api.example.com/users/${userId}`).then(r => r.json());
  const posts = yield fetch(`https://api.example.com/posts?userId=${user.id}`).then(r => r.json());
  return { user, posts };
}

function runGenerator(gen) {
  const g = gen();
  function next(data) {
    const result = g.next(data);
    if (result.done) return result.value;
    return result.value.then(next);
  }
  return next();
}

runGenerator(fetchUserDataWithYield).then(data => console.log(data));

对比:普通函数实现(使用Promise):#

function fetchUserDataWithoutYield(userId) {
  return fetch(`https://api.example.com/users/${userId}`)
    .then(r => r.json())
    .then(user => {
      return fetch(`https://api.example.com/posts?userId=${user.id}`)
        .then(r => r.json())
        .then(posts => {
          return { user, posts };
        });
    });
}

fetchUserDataWithoutYield(1).then(data => console.log(data));

优势对比:

  1. 使用yield的版本看起来更像同步代码,更容易理解和维护。
  2. 避免了”回调地狱”或深层嵌套的Promise。
  3. 错误处理更直观,可以使用try/catch。

示例4: 实现可中断的遍历#

使用yield的生成器函数:#

function* traverseWithYield(tree) {
  yield tree.value;
  for (let child of tree.children) {
    yield* traverseWithYield(child);
  }
}

const tree = {
  value: 'root',
  children: [
    { value: 'child1', children: [] },
    { value: 'child2', children: [{ value: 'grandchild', children: [] }] }
  ]
};

const gen = traverseWithYield(tree);
console.log(gen.next().value); // 'root'
console.log(gen.next().value); // 'child1'
console.log(gen.next().value); // 'child2'
console.log(gen.next().value); // 'grandchild'

对比:普通函数实现:#

function traverseWithoutYield(tree) {
  const result = [];
  function traverse(node) {
    result.push(node.value);
    for (let child of node.children) {
      traverse(child);
    }
  }
  traverse(tree);
  return result;
}

console.log(traverseWithoutYield(tree)); // ['root', 'child1', 'child2', 'grandchild']

优势对比:

  1. 使用yield的版本可以随时暂停和恢复遍历过程。
  2. 内存效率更高,特别是对于大型数据结构。
  3. 可以轻松实现”懒加载” 式的遍历。

yield的优势总结#

  1. 简化异步编程: 使复杂的异步操作看起来像同步代码。
  2. 惰性计算: 只在需要时才生成值,节省内存。
  3. 无限序列: 可以表示理论上无限的序列,而不会耗尽内存。
  4. 双向通信: 生成器函数可以暂停执行并等待外部输入。
  5. 状态管理: 生成器函数可以更容易地管理复杂的状态。
  6. 可中断性: 可以在任意点暂停和恢复执行。
  7. 代码组织: 使得某些算法的实现更加直观和易于理解。

结语#

yield关键字为JavaScript带来了强大的新功能。它不仅简化了异步编程,还为处理复杂数据结构和实现高级控制流提供了优雅的解决方案。虽然不是所有场景都需要使用yield,但在适当的情况下,它可以大大提高代码的可读性和效率。

ES6 yield关键字原来还可以这样用?
https://fuwari.vercel.app/posts/20240809/
作者
我也困了
发布于
2024-08-07
许可协议
CC BY-NC-SA 4.0