js生成器Generator
停止代码的“自动驾驶”:深入理解 JS 生成器 Generator
写前端这几年,咱们大多数时候习惯了函数一旦调用就一口气执行到底。就像按下了播放键,音乐不播完不罢休。但有时候,业务逻辑复杂得像绕线团,中间想停下来等个网络请求,或者保留现场下次再来,普通函数就显得力不从心了。这时候,Generator(生成器) 就像是一个能随时暂停、保存进度条的神奇按钮。
别被它的名字吓到,它本质上还是一个函数,只是写法上有点特殊。只要给 function 后面加个星号,这个函数就有了“暂停权”。配合使用 yield 关键字,你就能把函数切成一段一段的,想停就停,想走就走。
看个最简单的例子,感受一下这种“分段式”的执行流:
function* countNums() {
console.log('开始');
yield 1;
console.log('中间');
yield 2;
console.log('结束');
}
const generator = countNums();
console.log(generator.next().value); // 输出:开始 -> undefined (实际打印在 next 前)
// 注意:上面这行其实控制台先打印了“开始”,然后拿到 1
console.log(generator.next().value); // 继续从上次中断处运行
这段代码演示了核心逻辑:调用 generator.next() 会推进函数的执行,直到碰到下一个 yield。第一次调用时,函数体内的代码运行到 yield 1 停下;第二次调用,直接从 yield 1 之后继续跑,直到遇到下一次暂停或函数结束。这种机制让状态保持变得异常简单,你不需要手动维护一堆变量来记录走到哪一步了,生成器内部自己记着账呢。
很多人会问,现在 async/await 这么普及,写异步操作又优雅又易懂,还需要折腾生成器吗?这确实是个好问题。事实上,async/await 底层就是基于 Promise 和生成器的语法糖。但在某些特定场景下,原生生成器依然有不可替代的价值。
比如在构建复杂的状态机时。想象一个登录流程,用户输入账号、点击验证、等待服务端响应、根据结果跳转页面。如果用普通回调或 Promise 链,状态流转容易写成难以维护的大杂烩。而用生成器,你可以清晰地定义每一步的状态:
function* loginFlow() {
const account = yield 'Input Account';
if (!validate(account)) yield 'Retry';
const token = yield fetchToken(account);
return store(token);
}
在这个模型里,每一步都是一个明确的动作节点。配合像 Redux-Saga 这样的中间件,开发大型应用时,你可以用生成器去拦截 Action,以同步的代码风格处理副作用(比如 API 请求)。这就是生成器在生产环境中的真实落脚点——它把异步的坑,填成了看起来像是同步的路。
另外,懒加载也是生成器的一大戏份。如果你要处理一百万条数据,直接返回数组可能会卡死浏览器。但利用生成器的惰性求值特性,你可以按需计算,一次只返回一条,极大地节省了内存开销。这种对数据流的精细控制,是普通函数很难做到的。
当然,生成器也有门槛。调试它不像普通函数那样直观,堆栈追踪有时会让人觉得头大,而且过度滥用会让代码可读性下降。因此,除非你需要精细地控制执行流、构建状态机,或者是为了实现某种特定的迭代协议,否则在日常业务开发中,优先选择 async/await 依然是更稳妥的策略。
总结来说,生成器不是用来取代日常编程习惯的,它是工具箱里那把专门拧特殊螺丝的手枪。理解它如何挂起与恢复,如何在异步世界中保留上下文,能让你在面对复杂逻辑架构时多一种解题思路。不必为了用而生硬地套,但当你发现代码开始因为“状态难管”而变得臃肿时,不妨想想:是不是该找个机会让它暂停一下了。


还没有评论,来说两句吧...