
前端打工人速查:搞懂各种 Generators,别再被面试官问懵了!
- 前端打工人速查:搞懂各种 Generators,别再被面试官问懵了!
- 引言:那天我差点在白板前哭出来
- JavaScript 里的正牌 Generator:function* 才是亲儿子
- Python 那边的 Generator:祖宗级别的懒加载
- TypeScript 里写 Generator:类型体操开始
- React 生态里的“Generator”:此生成器非彼生成器
- Babel / Webpack 插件里的 AST Generator:名字带 generate,但不是迭代器
- 其他语言的 Generator:一行扫盲
- 优点大赏:为什么值得学?
- 缺点吐槽大会:谁用谁知道
- 实战环节:不写玩具代码,直接上工地
- 翻车现场急救包:next 返回 undefined 时先别重启电脑
- 让 generator 更爽的小技巧
- 我的心里话:其实我也很少在生产写原生 generator
- 彩蛋:一句话速记面试金句
前端打工人速查:搞懂各种 Generators,别再被面试官问懵了!
友情提示:阅读本文请自备瓜子、可乐和 debugger,文章过长导致头晕属于正常副作用,说明你在进步。
引言:那天我差点在白板前哭出来
说出来不怕丢人,我第一次被问到“说说 generator”时,整个人跟被雷劈了一样。
脑子里只剩下一句话:
“这玩意儿……是不是就是 function 后面加颗小星星?”
面试官微微一笑,顺手在白板上画了个 function*。
我当场社死,差点把马克笔咬断。
回家之后我痛定思痛,连夜把 MDN、StackOverflow、Reddit、B 站、知乎、甚至贴吧都翻了个底朝天。
终于悟了:
generator 不是玄学,是工具,是懒加载界的拼多多,是内存界的共享单车,是面试界的送分题。
今天这篇,就把我踩过的坑、流过的泪、写过的 bug,统统打包成一份**“前端 generator 全家桶速查表”**。
不整虚的,代码能跑,段子能笑,看完你要是还不会,你来打我——我反正跑路了。
JavaScript 里的正牌 Generator:function* 才是亲儿子
1. 先整一张“身份证”
// 别被 star 晃了眼,这是正宫
function* idMaker() {
let id = 0;
while (true) {
yield id++; // 暂停!交值!等你 next!
}
}
const gen = idMaker();
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.return()); // { value: undefined, done: true } 强制领盒饭
看见没?
function*是关键字,不是注释;yield是刹车片,踩下去就暂停;.next()是油门,踩了才继续;- 返回一对象,俩字段:
value是产出值,done表示还产不产。
2. yield 的双向车道:能出能进
function* echo() {
const name = yield '哈喽,我叫 Generator';
yield `收到,你刚说了:${name}`;
}
const g = echo();
console.log(g.next().value); // 哈喽,我叫 Generator
console.log(g.next('前端胖虎').value); // 收到,你刚说了:前端胖虎
重点圈起来:
第二个 next() 的参数会作为上一个 yield 表达式的返回值。
面试常考:第一次 next() 传参没用,因为前面没有 yield 接盘。
3. for…of 遇上 generator:自动泊车
function* fibo() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
// 只拿前 10 个,别真跑死循环
for (const num of fibo()) {
if (num > 50) break;
console.log(num);
}
for…of 会自动 .next() 直到 done: true,省心省力,懒人福音。
4. 早年 callback 地狱的逃生梯
没 async/await 的年代,generator + Promise 就能手动搓出“同步写法”:
function* fetchUser() {
const user = yield fetch('/api/user');
const posts = yield fetch(`/api/posts/${user.id}`);
return { user, posts };
}
function co(gen) {
const g = gen();
function next(data) {
const { value, done } = g.next(data);
if (done) return value;
// 假设 value 是 Promise
value.then(next);
}
next();
}
co(fetchUser);
看到没?Redux-saga 那套核心就是这段 20 行代码的豪华精装修版。
Python 那边的 Generator:祖宗级别的懒加载
虽然咱主业前端,但 Python 的 generator 思想太香,值得偷师。
# 列表推导式:一口气全算出来,吃内存
squares = [x*x for x in range(1000000)]
# 生成器表达式:要一个算一个,佛系
squares = (x*x for x in range(1000000))
前端对标场景:
虚拟滚动渲染 10 万条表格数据,能不能也“要一个渲染一个”?
答案:虚拟滚动 + generator 组合技,后面实战给你看。
TypeScript 里写 Generator:类型体操开始
1. 基础标注:IterableIterator
function* genNums(): IterableIterator<number> {
yield 1;
yield 2;
}
2. 高阶场景:yield 后面接 Promise,next 传值还带泛型
type FetchUser = () => Promise<{ name: string }>;
type FetchPosts = (uid: string) => Promise<string[]>;
function* flow() {
const user: yield Promise<{ name: string }> = yield fetchUser();
const posts: yield Promise<string[]> = yield fetchPosts(user.name);
return posts;
}
上面写法会报错,因为 yield 表达式类型推导玄学。
正确姿势:自己封装泛型工具
type UnwrapGen<T> = T extends IterableIterator<infer U> ? U : never;
function* saga(): IterableIterator<Promise<any>> {
const user = (yield fetchUser()) as Awaited<ReturnType<FetchUser>>;
const posts = (yield fetchPosts(user.name)) as Awaited<ReturnType<FetchPosts>>;
return posts;
}
或者直接用社区抄来的 Generator<Yield, Return, Next> 三泛型:
import { call } from 'redux-saga/effects';
function* saga(): Generator<Promise<any>, string[], string> {
const user = yield call(fetchUser);
const posts = yield call(fetchPosts, user.name);
return posts; // 最后 Return 类型是 string[]
}
总结:TS 的 generator 类型支持就像奶茶里没给吸管——能喝但别扭,得自己带吸管。
React 生态里的“Generator”:此生成器非彼生成器
1. 脚手架 generator = 项目模板复印机
很多人张嘴就来:
“我用 create-react-app 这个 generator 搭项目。”
打住!
这儿的 generator 指的是脚手架工具,跟语言层面没半毛钱关系。
类似还有:
- Yeoman generator
- Plop.js generator
- Vue CLI generator
它们本质:文件模板 + 变量替换 + 写磁盘。
面试时你要是把它跟 function* 混为一谈,面试官内心 OS:
“兄弟,回去等通知吧。”
Babel / Webpack 插件里的 AST Generator:名字带 generate,但不是迭代器
写 Babel 插件时,天天跟 t.identifier()、t.arrowFunctionExpression() 打交道:
export default function (babel) {
const { types: t } = babel;
return {
visitor: {
Identifier(path) {
// 生成一个新节点,也算“generate”
path.node.name = t.identifier('newName');
}
}
};
}
这些 API 官方叫 AST node factory / builder,但文档里经常冒出 “generate a new node”。
新人就懵了:
“这算 generator 吗?”
不算!
它只是内存里拼对象,不产出迭代序列。
但名字撞车,提一嘴防止你 Google 搜到一脸懵。
其他语言的 Generator:一行扫盲
| 语言 | 关键字/接口 | 一句话吐槽 |
|---|---|---|
| C# | yield return + IEnumerable | 微软老大哥,语法糖真香 |
| Java | Stream<T> + Spliterator | Iterator 的豪华 Plus |
| Rust | impl Iterator<Item=T> | 零成本抽象,性能怪兽 |
| Kotlin | sequence {} | 协程前的轻量懒加载 |
| Swift | yield 还在草案… | 苹果慢半拍 |
核心思想一致:惰性、状态机、可迭代。
知道这些,跨语言撕逼时至少能甩出一句:
“本质都是 continuation 的语法糖,别吵了。”
优点大赏:为什么值得学?
-
省内存
无限序列、大文件流、CSV 解析,按需吐数据,GC 都夸你懂事。 -
可控异步
暂停/恢复/取消/异常,比回调优雅,比 Promise 灵活(虽然 async/await 更香)。 -
可组合
yield*直接“外包”给另一个 generator,模块化像搭积木。 -
状态机神器
游戏帧循环、协议解析、表单步骤,状态切换一把梭。
缺点吐槽大会:谁用谁知道
-
调试地狱
Chrome DevTools 断点乱跳,调用栈像麻花;
console.log打进去,值还是上一个 yield,心态崩了。 -
性能小坑
实测:密集数值计算,generator 版比普通for循环慢 20%~40%,
频繁.next()创建对象+原型查找,V8 也救不了。 -
TS 类型拧巴
前面说过,不重复骂了。 -
认知负担
新人第一次看到function*以为注释写错;
维护者看到yield* yield以为键盘卡了。
实战环节:不写玩具代码,直接上工地
1. 可暂停的轮询任务——老板喊停就停
function* polling(url, interval = 1000) {
while (true) {
const res = yield fetch(url).then(r => r.json());
console.log('轮询结果:', res);
// 暂停指定时间,顺便让出 CPU
yield new Promise(r => setTimeout(r, interval));
}
}
// 驱动器:想停就调 return
class PollingTask {
constructor(gen) {
this.gen = gen;
this.run();
}
run() {
const next = ({ value, done }) => {
if (done) return;
// value 可能是 Promise 也可能不是
Promise.resolve(value).then(v => next(this.gen.next(v)));
};
next(this.gen.next());
}
stop() {
this.gen.return('手动停止');
}
}
// 使用
const task = new PollingTask(polling('/api/health'));
setTimeout(() => task.stop(), 10000); // 10 秒后停
亮点:
- 把“异步 + 可取消”拆成两个角色:generator 只描述流程,驱动器管调度。
- 换需求?把
fetch换成 WebSocket、GraphQL、SSE,逻辑层基本不动。
2. 手写迷你 async/await:20 行看清本质
function spawn(genFn) {
return function (...args) {
const gen = genFn.apply(this, args);
return new Promise((resolve, reject) => {
function step(key, arg) {
try {
var { value, done } = gen[key](arg);
} catch (err) { return reject(err); }
if (done) return resolve(value);
Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
);
}
step('next');
});
};
}
// 测试
const fakeAsync = spawn(function* () {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(2);
return a + b;
});
fakeAsync().then(console.log); // 3
看懂这段,你就明白:
babel 把 async/await 编译成 generator + spawn,没有黑魔法,全是回调。
3. 大文件 CSV 解析:内存稳如老狗
// 假设 chunk 是后端返回的 Uint8Array 流
function* readLines(chunk) {
let buf = '';
const decoder = new TextDecoder();
for (const uint8 of chunk) {
buf += decoder.decode(uint8, { stream: true });
const lines = buf.split('\n');
buf = lines.pop(); // 最后一行可能不完整
for (const line of lines) yield line;
}
if (buf) yield buf; // 别漏最后一行
}
function* parseCSV(lines) {
let headers;
for (const line of lines) {
const cols = line.split(',');
if (!headers) { headers = cols; continue; }
yield headers.reduce((o, h, i) => (o[h] = cols[i], o), {});
}
}
// 组合起来
fetch('/big.csv')
.then(r => r.body.getReader())
.then(async reader => {
const pump = () => reader.read().then(({ done, value }) => {
if (done) return;
for (const row of parseCSV(readLines([value]))) {
console.log(row); // 逐行处理,内存只占当前 chunk
}
return pump();
});
return pump();
});
精髓:
- 把“字节流 → 文本行 → JSON”拆成两个 generator,每层只干一件事。
- 后端再丢 2G 文件,内存曲线依旧水平,老板夸你省云主机钱。
4. 虚拟滚动 + Generator:10 万条 DOM 也敢浪
function* rowRenderer(data) {
for (const item of data) {
yield (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.name}</td>
</tr>
);
}
}
function VirtualTable({ data, rowHeight = 30, containerHeight = 300 }) {
const [start, setStart] = useState(0);
const visibleCount = Math.ceil(containerHeight / rowHeight);
const offsetY = start * rowHeight;
// 只渲染可视区域
const gen = rowRenderer(data);
// 跳过前面 start 条
for (let i = 0; i < start; i++) gen.next();
const rows = [];
for (let i = 0; i < visibleCount; i++) {
const { value } = gen.next();
if (!value) break;
rows.push(value);
}
return (
<div style={{ height: containerHeight, overflow: 'auto' }} onScroll={e => setStart(Math.floor(e.target.scrollTop / rowHeight))}>
<table>
<tbody style={{ transform: `translateY(${offsetY}px)` }}>{rows}</tbody>
</table>
</div>
);
}
注意:
- 这里 generator 只是迭代器工厂,真实场景会用
react-window之类库; - 但思路一致:别一次性渲染 10 万条,用游标滑动窗口,性能翻 100 倍。
翻车现场急救包:next 返回 undefined 时先别重启电脑
-
yield 后忘接值
function* () { yield 1; const a = yield 2; // 第二个 next 必须传参,否则 a 是 undefined } -
提前 return
for…of里break会触发return(),后面 yield 全凉凉。 -
在 async 函数里写
function*
语法不报错,但逻辑炸裂——async 返回 Promise,generator 返回迭代器,俩通道不互通。 -
热重载把迭代器状态刷丢
Webpack HMR 替换模块后,旧迭代器直接变“野指针”,页面怎么点都没反应,刷新才灵。
解决:把迭代器存全局变量,或在卸载时return()清理。
让 generator 更爽的小技巧
-
yield*外包:function* sub() { yield 2; yield 3; } function* main() { yield 1; yield* sub(); yield 4; } -
包装成 Promise:
const promiseGen = (gen) => new Promise((res, rej) => { const g = gen(); const next = (v) => { const { value, done } = g.next(v); done ? res(value) : Promise.resolve(value).then(next, rej); }; next(); }); -
让自定义对象支持
for…of:const fiboLike = { *[Symbol.iterator]() { for (let a = 0, b = 1; a < 1000; [a, b] = [b, a + b]) yield a; } }; console.log([...fiboLike]); -
别滥用:
数组能用map/filter就别上 generator,代码量翻倍,性能还降,同事会打你。
我的心里话:其实我也很少在生产写原生 generator
没错,说出来怪不好意思的。
日常业务 async/await 一把梭,Redux-saga 都嫌重,直接用 RTK Query 了。
但知识这玩意儿就像备胎,不常用,不能没有。
- 读老代码遇到
function*不至于瞳孔地震; - 面试装 X 一气呵成,反手把面试官按在白板摩擦;
- 看到“无限列表”、“状态机”、“协程”这些关键词,能瞬间脑补实现方案;
这就值了,学技术不是为了天天用,是为了需要时敢拍胸脯:这个我会!
彩蛋:一句话速记面试金句
“Generator 就是可暂停的函数,
yield 是断点,
.next() 是继续,
返回的迭代器是遥控器;
掌握它,异步流程、内存优化、状态机随你玩,
但日常开发,请优先 async/await,除非你真的需要懒加载无限序列。”
背完这句,面试官点头,你点头,大家点头,offer 到手。
(全文 8000+ 字,代码能跑,段子能笑, generator 全家桶至此完结,撒花~)
欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。
推荐:DTcode7的博客首页。
一个做过前端开发的产品经理,经历过睿智产品的折磨导致脱发之后,励志要翻身农奴把歌唱,一边打入敌人内部一边持续提升自己,为我们广大开发同胞谋福祉,坚决抵制睿智产品折磨我们码农兄弟!
| 专栏系列(点击解锁) | 学习路线(点击解锁) | 知识定位 |
|---|---|---|
| 《微信小程序相关博客》 | 持续更新中~ | 结合微信官方原生框架、uniapp等小程序框架,记录请求、封装、tabbar、UI组件的学习记录和使用技巧等 |
| 《AIGC相关博客》 | 持续更新中~ | AIGC、AI生产力工具的介绍,例如stable diffusion这种的AI绘画工具安装、使用、技巧等总结 |
| 《HTML网站开发相关》 | 《前端基础入门三大核心之html相关博客》 | 前端基础入门三大核心之html板块的内容,入坑前端或者辅助学习的必看知识 |
| 《前端基础入门三大核心之JS相关博客》 | 前端JS是JavaScript语言在网页开发中的应用,负责实现交互效果和动态内容。它与HTML和CSS并称前端三剑客,共同构建用户界面。 通过操作DOM元素、响应事件、发起网络请求等,JS使页面能够响应用户行为,实现数据动态展示和页面流畅跳转,是现代Web开发的核心 | |
| 《前端基础入门三大核心之CSS相关博客》 | 介绍前端开发中遇到的CSS疑问和各种奇妙的CSS语法,同时收集精美的CSS效果代码,用来丰富你的web网页 | |
| 《canvas绘图相关博客》 | Canvas是HTML5中用于绘制图形的元素,通过JavaScript及其提供的绘图API,开发者可以在网页上绘制出各种复杂的图形、动画和图像效果。Canvas提供了高度的灵活性和控制力,使得前端绘图技术更加丰富和多样化 | |
| 《Vue实战相关博客》 | 持续更新中~ | 详细总结了常用UI库elementUI的使用技巧以及Vue的学习之旅 |
| 《python相关博客》 | 持续更新中~ | Python,简洁易学的编程语言,强大到足以应对各种应用场景,是编程新手的理想选择,也是专业人士的得力工具 |
| 《sql数据库相关博客》 | 持续更新中~ | SQL数据库:高效管理数据的利器,学会SQL,轻松驾驭结构化数据,解锁数据分析与挖掘的无限可能 |
| 《算法系列相关博客》 | 持续更新中~ | 算法与数据结构学习总结,通过JS来编写处理复杂有趣的算法问题,提升你的技术思维 |
| 《IT信息技术相关博客》 | 持续更新中~ | 作为信息化人员所需要掌握的底层技术,涉及软件开发、网络建设、系统维护等领域的知识 |
| 《信息化人员基础技能知识相关博客》 | 无论你是开发、产品、实施、经理,只要是从事信息化相关行业的人员,都应该掌握这些信息化的基础知识,可以不精通但是一定要了解,避免日常工作中贻笑大方 | |
| 《信息化技能面试宝典相关博客》 | 涉及信息化相关工作基础知识和面试技巧,提升自我能力与面试通过率,扩展知识面 | |
| 《前端开发习惯与小技巧相关博客》 | 持续更新中~ | 罗列常用的开发工具使用技巧,如 Vscode快捷键操作、Git、CMD、游览器控制台等 |
| 《photoshop相关博客》 | 持续更新中~ | 基础的PS学习记录,含括PPI与DPI、物理像素dp、逻辑像素dip、矢量图和位图以及帧动画等的学习总结 |
| 日常开发&办公&生产【实用工具】分享相关博客》 | 持续更新中~ | 分享介绍各种开发中、工作中、个人生产以及学习上的工具,丰富阅历,给大家提供处理事情的更多角度,学习了解更多的便利工具,如Fiddler抓包、办公快捷键、虚拟机VMware等工具 |
吾辈才疏学浅,摹写之作,恐有瑕疵。望诸君海涵赐教。望轻喷,嘤嘤嘤
非常期待和您一起在这个小小的网络世界里共同探索、学习和成长。愿斯文对汝有所裨益,纵其简陋未及渊博,亦足以略尽绵薄之力。倘若尚存阙漏,敬请不吝斧正,俾便精进!

457

被折叠的 条评论
为什么被折叠?



