news 2026/4/16 11:00:57

ES6生成器函数入门必看:基础语法与应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ES6生成器函数入门必看:基础语法与应用

生成器函数:被低估的 JavaScript 控制流利器

你有没有遇到过这样的场景?

写异步代码时,明明逻辑很简单,却要被.then()套来套去搞得晕头转向;处理大量数据时,内存爆了才发现不该一次性加载全部内容;想实现一个无限序列(比如自增 ID),结果发现传统函数根本“停不下来”。

这些问题的背后,其实都指向同一个根源:JavaScript 缺乏对函数执行过程的精细控制能力。

直到 ES6 引入了function*yield—— 也就是我们今天要聊的主角:生成器函数(Generator Function)

它不像const、箭头函数那样直观易用,也不像async/await那样广为人知。但它却是现代前端框架底层机制的重要基石。Redux-Saga 的任务调度、Koa 的洋葱模型、甚至早期 async 函数的 polyfill,背后都有它的影子。

更重要的是,一旦你真正理解了生成器,你就掌握了“暂停函数”这种在其他语言中属于高级特性的能力。而这,正是通往更高级编程范式的大门。


什么是生成器?从一次“不会立即执行”的调用说起

我们先来看一段看似普通、实则诡异的代码:

function* hello() { console.log('Hello'); yield 'world'; console.log('Goodbye'); } const gen = hello();

猜猜看,上面这段代码运行后会输出什么?

答案是:什么都不会输出。

这和普通函数完全不同。通常情况下,函数一调用就会开始执行。但生成器函数不一样——它返回的是一个生成器对象(Generator Object),而不是直接执行函数体。

这个对象是个“懒家伙”,只有当你主动叫它动一下,它才会往前走一步。

怎么动?靠.next()方法。

console.log(gen.next()); // 输出: Hello // { value: 'world', done: false } console.log(gen.next()); // 输出: Goodbye // { value: undefined, done: true }

第一次调用.next(),函数才真正启动,执行到第一个yield暂停,并把'world'作为value返回。

第二次调用.next(),从上次暂停的地方继续,打印'Goodbye',走到函数末尾,此时done: true,表示任务完成。

这种“按需驱动”的行为,就是生成器最核心的能力:可中断执行


它不只是能暂停:双向通信与状态保持

很多人以为yield只是用来“吐出值”的,其实它还能“接收值”。

看这个例子:

function* talker() { const name = yield '你的名字是?'; console.log(`你好,${name}!`); const age = yield '你多大了?'; console.log(`${age}岁啊,真年轻!`); }

现在我们一步步推进:

const t = talker(); console.log(t.next()); // { value: '你的名字是?', done: false } console.log(t.next('张三')); // 你好,张三! // { value: '你多大了?', done: false } console.log(t.next(25)); // 25岁啊,真年轻! // { value: undefined, done: true }

注意第二、第三次调用.next(value)时传入的参数,它们会“填进”对应的yield表达式中。也就是说:

const name = yield '你的名字是?';

这行代码的意思其实是:“我先把问题抛出去,等外面给我答案了,再赋值给name”。

这就形成了函数内外的双向通信,有点像协程之间的协作对话。

而且在整个过程中,函数内部的状态(比如变量name、执行位置)都被完整保留着,不会因为暂停而丢失。这是闭包也难以做到的精准控制。


真正的价值:惰性求值与无限序列

生成器最大的优势之一,是它可以实现惰性计算(Lazy Evaluation)—— 只有你需要的时候才去算下一个值。

举个经典例子:斐波那契数列。

如果用数组预生成前 10000 项,不仅耗时还占内存。但如果用生成器呢?

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

就这么简单。你可以让它一直生成下去,但每一步都是按需触发。

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

甚至可以用for...of遍历(记得加终止条件!):

let count = 0; for (const n of fibonacci()) { console.log(n); if (++count === 10) break; }

类似地,你可以轻松写出一个永不枯竭的唯一 ID 生成器:

function* idMaker() { let id = 1; while (true) yield id++; }

这些在传统函数中几乎无法优雅实现的功能,在生成器面前变得轻而易举。


曾经的异步救星:生成器 + Promise 的黄金组合

虽然现在大家习惯用async/await,但在 ES2017 之前,JavaScript 并没有原生的“同步写法异步逻辑”。那时候,开发者是怎么破局的?

答案就是:生成器 + Promise + 执行器(Runner)

设想这样一个需求:

  1. 获取用户信息;
  2. 根据用户 ID 获取订单;
  3. 根据订单 ID 获取商品详情。

用 Promise 写,层层嵌套或链式调用,容易混乱。而用生成器,可以写出看起来像同步的代码:

function fetch(url) { return new Promise(resolve => setTimeout(() => resolve(`Data from ${url}`), 1000) ); } function* main() { console.log('开始获取用户...'); const user = yield fetch('/user'); console.log(user); console.log('开始获取订单...'); const order = yield fetch(`/order?uid=${user}`); console.log(order); console.log('开始获取商品...'); const product = yield fetch(`/product?oid=${order}`); console.log(product); return '全部完成'; }

这段代码看起来是不是很像async/await?唯一的区别是用了yield而不是await

但它不能直接运行,需要一个“推动器”来自动调用.next()并处理 Promise:

function run(generatorFn) { const iterator = generatorFn(); function go(result) { if (result.done) return Promise.resolve(result.value); // 把 value 当作 Promise 处理 return Promise.resolve(result.value).then(data => { return go(iterator.next(data)); }); } return go(iterator.next()); }

然后就可以这样运行:

run(main).then(console.log); // 依次输出... // 最终输出: 全部完成

这套模式后来被封装成著名的库co,也成为async/await的设计原型。

至今,Redux-Saga依然基于这一思想工作:

function* fetchUserSaga(action) { try { const user = yield call(fetch, `/api/users/${action.payload.id}`); const data = yield call([user, user.json]); yield put({ type: 'USER_LOADED', payload: data }); } catch (err) { yield put({ type: 'LOAD_FAILED', error: err }); } }

这里的callput都是 Effect 创建函数,Saga 中间件负责解释并执行它们,本质上还是通过.next()推动流程前进。


实战技巧:别踩这几个坑

尽管强大,但生成器并不适合所有场景。使用时要注意以下几点:

✅ 什么时候该用?

  • 复杂流程编排:多个异步操作之间有复杂依赖或分支逻辑。
  • 监听多个事件源:如 Redux-Saga 中takeEverytakeLatest监听 action 流。
  • 大数据流处理:逐条读取文件行、数据库记录,避免内存溢出。
  • 状态机建模:游戏回合、UI 步骤引导等有限状态切换。

❌ 什么时候不该用?

  • 简单异步请求:完全可以用async/await替代,更简洁。
  • 高频调用的小函数:生成器有一定性能开销,小题大做。
  • IE 兼容需求:IE 全系列不支持,必须转译。

⚠️ 调试注意事项

生成器的调用栈和普通函数不同,断点可能会“跳来跳去”。建议:

  • 使用debugger语句辅助定位;
  • 在支持的环境中启用“Async Stack Traces”;
  • 结合日志输出中间状态,尤其是yield前后的上下文。

为什么今天还要学生成器?

你可能会问:既然有了async/await,为什么还要费劲学生成器?

因为:

async/await是语法糖,生成器是底层机制。

就像你想精通 React,就不能只懂 JSX 而不懂 Virtual DOM 一样。不了解生成器,你就看不懂 Redux-Saga 的源码,也无法真正理解“副作用是如何被管理的”。

此外,生成器的能力远不止异步控制:

  • 它可以作为迭代器工厂,为自定义数据结构提供遍历支持;
  • 可以结合 Symbol.iterator 实现类的可迭代化;
  • 在算法题中用于回溯、生成组合排列等场景也非常高效。

更重要的是,它体现了一种编程哲学:将控制权交还给外部调度器,实现更灵活的任务管理。

这种“协作式多任务”(Cooperative Multitasking)的思想,在 Node.js、Deno、甚至 WASM 中都有广泛应用前景。


小结:掌握生成器,就掌握了 JS 的“暂停键”

生成器函数或许不再是日常开发中的首选工具,但它的重要性从未减弱。

它是 JavaScript 中首个提供函数级暂停与恢复能力的特性,开启了协程式编程的大门。

通过function*yield,我们可以:

  • 实现惰性求值,高效处理无限序列;
  • 构建自定义迭代器,增强数据抽象能力;
  • 编写线性风格的异步逻辑,提升代码可读性;
  • 支撑复杂的状态管理架构,如 Redux-Saga。

即使你不亲自写生成器,了解其原理也能帮助你更好地使用基于它的库,理解现代 JS 异步演进的脉络。

所以,下次当你看到function*的时候,不要绕道走。停下来,试着读懂它背后的控制流设计——那可能是你迈向高级 JavaScript 开发的关键一步。

如果你正在尝试理解 Redux-Saga 或 Koa 的中间件机制,不妨动手实现一个简单的 generator runner,你会发现,原来“魔法”背后,不过是一次.next()的推动而已。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:00:45

终极指南:简单5步搭建专属私有音乐库

终极指南:简单5步搭建专属私有音乐库 【免费下载链接】any-listen A cross-platform private song playback service. 项目地址: https://gitcode.com/gh_mirrors/an/any-listen 🎵 还在为音乐平台版权限制烦恼吗?想要一个完全属于自己…

作者头像 李华
网站建设 2026/4/14 16:38:49

微信聊天记录终极备份方案:5分钟永久保存珍贵回忆

微信聊天记录终极备份方案:5分钟永久保存珍贵回忆 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending/we/WeChatMsg…

作者头像 李华
网站建设 2026/4/14 18:39:31

usb_burning_tool刷机工具如何写入镜像文件?一文说清

从零开始掌握Amlogic刷机:如何用usb_burning_tool写入镜像文件?你有没有遇到过这样的情况——电视盒子卡在开机画面动弹不得,或者刷了个自定义系统后彻底“变砖”,连ADB都进不去?这时候,大多数教程推荐的方…

作者头像 李华
网站建设 2026/4/9 2:56:25

通俗解释多层感知机实现异或逻辑门全过程

多层感知机如何“算出”异或?一文讲透神经网络的逻辑魔法 你有没有想过,计算机最基本的运算单元——逻辑门,其实也能用神经网络来实现? 我们都知道,与门、或门这些基础电路可以通过简单的电子元件搭建。但有一个特别的…

作者头像 李华
网站建设 2026/4/15 15:26:53

SGLang-v0.5.6体验报告:云端2小时深度测试仅花2块钱

SGLang-v0.5.6体验报告:云端2小时深度测试仅花2块钱 你有没有遇到过这样的情况:作为一个技术投资人,手头有一个AI项目看起来潜力巨大,想快速验证它的性能和稳定性,但又不想为了短短几天的测试去租服务器、搭环境、买G…

作者头像 李华