中级前端进阶方向 第六步)深入 javascript 高级特性Generator生成器,附案例源码
Generator 是 JavaScript 中一个非常强大且富有表现力的特性。虽然它在日常异步编程中的角色已被 async/await 取代,但在创建自定义迭代器、处理无限数据结构、实现状态机等场景中,它依然是一个非常优雅和实用的工具。理解 Generator 对于深入理解 JavaScript 的异步编程发展史和迭代器协议也至关重要
《中级前端进阶方向 第一步)JavaScript 微任务 / 宏任务机制》
《中级前端进阶方向 第二步)深入事件循环(Event Loop)》
《中级前端进阶方向 第三步)深入javascript原型链,附学习代码》
《中级前端进阶方向 第四步)深入javascript 闭包、this 、作用域提升,附学习案例源码》
《中级前端进阶方向 第五步)深入 javascript高级特性Proxy,附学习案例源码》
--🖍《延伸扩展》----------------------------------------------------------------------------------------
《中级前端进阶方向 延伸扩展一)javascript 私有状态/工厂函数/回调中保持局部状态/防抖与节流/IIFE 捕获》
1. 概念与语法(一句话)
Generator(生成器)是能“暂停/恢复”执行的函数,用 function*
声明,内部通过 yield
发出值。调用 generator 函数并不会执行函数体,而是返回一个 Generator 对象(同时是 Iterator 与 Iterable),通过 .next()
驱动执行。
function* gen(){
yield 1;
yield 2;
return 3;
}
const it = gen();
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: true }
2. 核心特性(pause/resume + 保留执行上下文)
-
yield
:暂停并“产出”一个值给外部(外部可通过.next()
取得)。 -
调用
.next()
:恢复函数执行,直到下一个yield
或return
或结束。 -
Generator 保留函数执行上下文(局部变量、作用域链、当前位置),所以它天然是一个状态机。
3. .next(value)
的细节(向生成器“注入”值)
-
yield expr
表达式自身的值,来自下一次.next(value)
传入的value
。 -
第一次调用
it.next(arg)
时传入的arg
会被忽略(因为尚未停在任何yield
处去接受值)。
示例:
function* g(){
const a = yield 'first';
console.log('a=', a);
const b = yield 'second';
console.log('b=', b);
return 'done';
}
const it = g();
console.log(it.next('x')); // {value: 'first', done: false} // 'x' 被忽略
console.log(it.next(10)); // logs a=10, returns {value:'second', done:false}
console.log(it.next(20)); // logs b=20, returns {value:'done', done:true}
4. 三个控制方法:.next()
/ .throw()
/ .return()
-
it.next(value)
:恢复并向上一个yield
注入value
。 -
it.throw(error)
:在暂停的yield
表达式处抛出一个异常,若 generator 内部捕获则处理,否则向外抛出(迭代终止)。 -
it.return(value)
:强制结束 generator,返回{ value, done: true }
,会触发finally
块执行(如果有),随后关闭生成器。
示例(错误注入):
function* g(){
try {
yield 1;
} catch(e) {
console.log('caught', e);
}
yield 2;
}
const it = g();
it.next(); // {value:1}
it.throw(new Error('x')); // generator 内 catch 到错误,继续执行,下一 yield 返回 2
it.next(); // {value:2}
示例(return 与 finally):
function* g(){
try {
yield 1;
yield 2;
} finally {
console.log('cleanup');
}
}
const it = g();
it.next(); // {value:1}
it.return('X'); // prints "cleanup", returns {value:'X', done:true}
5. yield*
(委托)与返回值
-
yield* iterable
用来委托给另一个可迭代对象(或 generator)。它会逐项yield
传递并且当 inner generatorreturn
一个值时,该值会作为yield*
的返回值(可被外层赋值接收)。
function* inner(){
yield 2;
return 3;
}
function* outer(){
yield 1;
const r = yield* inner(); // r === 3
console.log('inner returned', r);
yield 4;
}
for (const v of outer()) console.log(v); // 1 2 4
注意:for...of
会忽略最终 return
的值(所以 3
不会被 for...of
输出),但 yield*
可以捕获这个 return。
6. Generator 是 Iterator & Iterable
Generator 对象实现了 Iterator 接口(有 .next()
)并可通过 Symbol.iterator
返回自身,因此可以直接用于 for...of
、解构、[...it]
:
function* gen(){ yield 1; yield 2; }
const it = gen();
console.log([...it]); // [1,2]
7. 典型应用场景(为什么要用 Generator)
-
惰性序列 / 无限序列(按需计算):如流式数据、斐波那契、ID 生成器。
-
实现自定义迭代器(比手写 state 机更简洁)。
-
协程 / 协作式多任务调度(把函数暂停点当作切换点)。
-
异步控制流(历史):用 generator + Promise 写同步风格的异步逻辑(
co
库、早期的redux-saga
灵感)。 -
中间件流水线 / 简洁状态机:生成器天然保持状态、可暂停/恢复,适合实现解析器、协议栈等。
示例1:优雅的迭代器 Generator 是创建迭代器的语法糖,极大地简化了迭代器的编写。
// 不用 Generator,实现一个范围迭代器很麻烦
// 用 Generator 非常简单
function* range(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for (let num of range(1, 5)) {
console.log(num); // 依次输出 1, 2, 3, 4, 5
}
示例2:解决 回掉地狱 Generator 历史上一个非常重要的用途,通常需要与 Promise
和运行器(Runner)函数配合(如 co
库)。这实际上是 async/await
的雏形
// 一个模拟的异步函数
function fetchData(url) {
return new Promise(resolve => setTimeout(() => resolve(`Data from ${url}`), 1000));
}
// Generator 定义主流程
function* mainFlow() {
const data1 = yield fetchData('/api/user');
console.log(data1); // "Data from /api/user" (1秒后)
const data2 = yield fetchData('/api/posts');
console.log(data2); // "Data from /api/posts" (又1秒后)
return 'All done!';
}
// 一个简易的运行器函数
function run(generator) {
const gen = generator();
function handle(result) {
if (result.done) return Promise.resolve(result.value);
// 假设 yield 出来的都是 Promise
return Promise.resolve(result.value)
.then(data => handle(gen.next(data))) // 将 Promise 的结果传回 Generator
.catch(err => gen.throw(err)); // 将错误抛回 Generator
}
return handle(gen.next());
}
run(mainFlow).then(console.log); // 最终输出: "All done!"
示例3:惰性斐波那契
function* fib(){
let [a,b] = [0,1];
while(true){
yield a;
[a,b] = [b, a+b];
}
}
const f = fib();
console.log(f.next().value); // 0
console.log(f.next().value); // 1
示例4:生成无线数据流
function* naturalNumbers() {
let n = 1;
while (true) { // 无限循环!
yield n++;
}
}
const numbers = naturalNumbers();
console.log(numbers.next().value); // 1
console.log(numbers.next().value); // 2
console.log(numbers.next().value); // 3
// ... 可以一直取下去
示例4:生成一个 状态机
function* trafficLight() {
while (true) {
yield 'Red'; // 停
yield 'Yellow'; // 等待
yield 'Green'; // 行
yield 'Yellow'; // 等待
}
}
const light = trafficLight();
console.log(light.next().value); // 'Red'
console.log(light.next().value); // 'Yellow'
console.log(light.next().value); // 'Green'
console.log(light.next().value); // 'Yellow'
console.log(light.next().value); // 'Red' (循环回来了)
// ...
8. 生成器 + Promise:把异步写成同步风格(co 模式)
Generator 可 yield
Promise,再用 runner(执行器)自动驱动 .next()
,把异步链写成同步语义:
function run(genFn){
const gen = genFn();
function step(nextF){
let res;
try {
res = nextF();
} catch(err) {
return Promise.reject(err);
}
if (res.done) return Promise.resolve(res.value);
return Promise.resolve(res.value).then(
v => step(() => gen.next(v)),
e => step(() => gen.throw(e))
);
}
return step(() => gen.next());
}
// 使用
run(function*(){
const r1 = yield fetchData(); // 假设返回 Promise
const r2 = yield fetchMore(r1);
return r2;
});
说明:现在 async/await 已取代这种用法的大部分场景,但理解它很有帮助(也能理解 async/await 的实现原理)。
9. Async Generator(异步生成器)与 for await...of
-
async function*
声明会创建 异步迭代器,.next()
返回 Promise,使用for await (const v of asyncIterable)
遍历。
async function* readLines(stream){
for await (const chunk of stream) {
yield chunk.toString();
}
}
(async () => {
for await (const line of readLines(someAsyncStream)) {
console.log(line);
}
})();
用途:逐块/逐行读取网络或文件流,方便处理异步流式数据。
10. 错误处理与生命周期细节(常见面)
-
it.throw(err)
:把错误注入到生成器内部的暂停点,若未被捕获则向外抛。 -
生成器内的
try...finally
会在return()
/throw()
时保证finally
执行(可做清理)。 -
for...of
循环在提前中断(break)时,会调用 iterator 的return()
(如果存在),从而触发 generator 的finally
清理逻辑。
示例(for..of 中断会触发 finally):
function* g(){
try {
yield 1;
yield 2;
} finally {
console.log('cleanup');
}
}
for (const v of g()) {
console.log(v);
break; // 这里会触发 generator.return(),进而执行 finally
}
// prints: 1 \n cleanup
11. 与 async/await
的比较
-
async/await
在语义和易用性上取代了 Generator + Promise + Runner 的模式来处理异步,使用更直观;内部实现其实和generator + runner
思路相似。 -
Generator 更通用(不仅是异步):可控制执行流、可注入值、可做协程/可暂停的状态机。
-
对于普通异步场景,优先用
async/await
;需要更底层控制(如实现库、DSL、复杂中间件)时,Generator 很有用(例如redux-saga
用 generator 实现副作用描述并可测试)
12. 实战示例集合(带输出解释)
12.1 入门详解案例
function* myGenerator() {
console.log('开始执行');
yield 'Hello'; // 第一次暂停
console.log('从第一次暂停恢复');
yield 'World'; // 第二次暂停
console.log('从第二次暂停恢复');
return 'Ending'; // 结束
}
const gen = myGenerator(); // 创建 Generator 对象,无日志输出
// 第一次调用 next()
let result = gen.next();
console.log(result); // { value: 'Hello', done: false }
// 控制台输出: "开始执行"
// 第二次调用 next()
result = gen.next();
console.log(result); // { value: 'World', done: false }
// 控制台输出: "从第一次暂停恢复"
// 第三次调用 next()
result = gen.next();
console.log(result); // { value: 'Ending', done: true }
// 控制台输出: "从第二次暂停恢复"
// 第四次调用 next()
result = gen.next();
console.log(result); // { value: undefined, done: true }
// 此后无论调用多少次 next(), 都返回 { value: undefined, done: true }
12.2 用 generator 实现简单中间件流水线
function* pipeline(initial){
let val = initial;
while(true){
const fn = yield val; // 外部传入处理函数
if (!fn) break;
val = fn(val);
}
return val;
}
const p = pipeline(1);
console.log(p.next().value); // 1
console.log(p.next(x=>x+2).value); // 3
console.log(p.next(x=>x*10).value);// 30
console.log(p.next().done); // true (结束)
12.3 发送值 & 初次 next 忽略参数
function* g(){
console.log('start');
const x = yield 'A';
console.log('got x =', x);
return 'done';
}
const it = g();
console.log(it.next('ignored')); // {value:'A', done:false} // 'ignored' 被忽略
console.log(it.next(42)); // logs 'got x = 42', returns {value:'done', done:true}
13. 性能 & 实践建议
-
Generator
有运行时开销(保存上下文、暂停点),不要在极热路径(每帧、超高频循环)中大量创建/恢复。 -
对普通异步代码用
async/await
更简洁、更被广泛接受。Generator 更适合库/框架/DSL级别使用(例如流式迭代、协程、redux-saga 类型的场景)。 -
使用
yield*
提高组合性与可读性。 -
始终在可能会中断的逻辑中写
try...finally
做清理,避免资源泄漏。 -
一个 Generator 对象一旦遍历完成(
done: true
),就无法再重新使用。你需要重新调用 Generator 函数来创建一个新的对象。
14. 常见面试/考点总结(速查)
-
function*
、yield
、.next()
的基本语义。 -
.next(value)
如何把value
传值给上一个yield
。 -
.throw()
的用途与行为(注入异常)。 -
.return()
强制结束并触发finally
。 -
yield*
用法与 inner-return 的捕获。 -
Generator 是 Iterable & Iterator,可用于
for..of
。 -
用 generator 实现异步流程控制(co 模式),与
async/await
的关系。 -
async function*
+for await..of
用于异步迭代流。
更多推荐
所有评论(0)