生成器函数与 yield:掌控函数执行的 “暂停键”

生成器函数与 yield:掌控函数执行的 “暂停键”
Touko生成器函数是 ES6 里很实用的特性,而
yield就像给函数装了个 “暂停开关”—— 能让函数执行到一半停下来,需要的时候再接着跑。这种 “可控的执行流程”,在处理异步操作、遍历大数据、实现状态切换时特别好用。
一、生成器:能 “中场休息” 的特殊函数
1.1 什么是生成器函数?
生成器函数是可以暂停执行、后续恢复的特殊函数,它的语法很容易识别:用 function* 定义,内部用 yield 控制暂停。
举个最简单的例子,感受下它的 “暂停 - 恢复” 能力:
1 | // 用 function* 定义生成器函数 |
1.2 生成器对象的核心方法
生成器对象(比如上面的 generator)有三个关键方法,用来控制函数执行:
| 方法 | 作用 | 示例 |
|---|---|---|
next(value) |
恢复执行,可给上一个 yield 传值 |
generator.next('传入的数据') |
return(value) |
提前结束生成器,后续 next() 都会返回 done: true |
generator.return('提前终止') |
throw(error) |
向生成器内部抛错,可在生成器里用 try/catch 捕获 |
generator.throw(new Error('出错了')) |
1.3 必须注意的两个点
箭头函数不能做生成器
箭头函数没有自己的this和arguments,也不支持yield,所以没法写成()* => {}这种形式。调用生成器不立即执行
和普通函数不同,stepGenerator()调用后不会跑函数体,而是先返回生成器对象 —— 必须调用next()才会开始执行。
二、yield:生成器的 “暂停开关” 与 “数据通道”
yield 不只是 “暂停键”,还能在生成器和外部之间传递数据,相当于一个 “双向通道”。
2.1 yield 的两个核心作用
- 暂停执行,向外传值
yield 表达式会让函数暂停,同时把 “表达式的值” 传给外部(通过next().value获取):
1 | function* fruitGenerator() { |
- 接收外部传入的值
外部调用next(值)时,这个 “值” 会作为上一个yield表达式的返回值,传给生成器内部:
1 | function* chatGenerator() { |
注意: next(值) 的 “值”只给上一个 yield。如果是第一次调用 next(),传值是无效的 —— 因为此时还没有执行过任何 yield。
2.2 yield 的使用限制
yield 只能在生成器函数内部用,哪怕是生成器里的嵌套函数也不行,否则会报错:
1 | function* wrongGenerator(items) { |
原理和
return类似:嵌套函数的return不能直接让外层函数返回,yield也无法跨越函数边界。
三、生成器的实际用途:这些场景用它很顺手
生成器不是花架子,实际开发中很多场景都能用到,分享几个高频用法:
3.1 自定义迭代器:轻松遍历复杂数据
JavaScript 里的 for...of 循环需要 “可迭代对象”(比如数组、字符串),而生成器可以轻松创建自定义的迭代器,不用手动实现 Symbol.iterator。
比如实现一个 “指定范围的数字迭代器”:
1 | // 生成 从 start 到 end、步长为 step 的数字 |
3.2 无限序列:按需生成,不占内存
如果需要无限循环的序列(比如斐波那契数列),用普通函数会陷入死循环,但生成器可以 “按需产出”,想要多少要多少:
1 | // 生成无限的斐波那契数列 |
3.3 异步流程管理:async / await 之前的方案
在 async / await(👉 Async / Await:用同步的方式写异步,真香!)普及前,生成器常用来简化异步代码(避免回调地狱)。核心思路是:用 yield 暂停等待异步结果,拿到结果后再恢复执行。
1 | // 模拟两个异步请求 |
现在虽然 async/await 更常用,但理解这种思路,能帮你更好地掌握异步编程的本质。
3.4 状态机:清晰管理状态切换
比如交通灯的 “红 → 绿 → 黄” 循环,用生成器实现状态切换,逻辑特别清晰:
1 | // 交通灯状态机 |
四、yield*:生成器的 “委托执行”
如果一个生成器需要调用另一个生成器,可以用 yield* 实现 “委托”—— 让被调用的生成器先执行完,再回到当前生成器。
4.1 基本用法:委托执行其他生成器
1 | // 生成器 A |
可以看到,yield* generatorA() 会让 generatorA 的所有 yield 先执行,再继续 generatorB 后续的代码。
4.2 实用场景:递归遍历树形结构
比如遍历 DOM 树、文件夹目录这种层级结构,用 yield* 递归委托特别方便:
1 | // 遍历树形结构(比如 DOM 树) |
五、生成器最佳实践:避坑与优化
5.1 资源清理:用 try…finally 确保释放
如果生成器里用到了需要手动释放的资源(比如文件句柄、网络连接),一定要用 try...finally—— 哪怕生成器被提前终止(比如调用 return()),finally 里的代码也会执行,避免资源泄漏。
1 | function* resourceGenerator() { |
5.2 避免无限循环:加安全边界
除非确实需要 “无限序列”(比如斐波那契),否则一定要给生成器加终止条件 —— 避免不小心写成死循环,导致内存溢出。
1 | // 安全的生成器:最多生成 100 个值 |
5.3 异常处理:用 throw () 抛错,try / catch 捕获
如果生成器执行中需要处理错误,可以在外部调用 generator.throw(错误),然后在生成器内部用 try/catch 捕获:
1 | function* errorHandleGenerator() { |
六、生成器 vs 普通函数:核心差异对比
| 特性 | 普通函数 | 生成器函数 |
|---|---|---|
| 执行流程 | 一旦开始,必须执行完 | 可暂停(yield)、可恢复(next()) |
| 返回值 | 只能返回一次值(return) |
可多次返回值(yield),最后一次 return 是收尾 |
| 内存效率 | 一次性处理所有数据,大数据易内存溢出 | 按需生成值,处理大数据更高效 |
| 状态保持 | 执行完后局部变量销毁,不保持状态 | 暂停时保留局部变量状态,恢复后继续使用 |
| 定义语法 | function 函数名() {} |
function* 函数名() {} |
生成器的核心价值在于 “可控的执行流程” 和 “按需产出数据”—— 它不像 async/await 那样专门解决异步问题,而是在迭代、状态管理、大数据处理等场景都能发挥作用。如果你需要更灵活地控制函数执行,或者想简化复杂的遍历逻辑,不妨试试生成器~










