🤙《中级前端进阶方向指南》

《中级前端进阶方向 第一步)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():恢复函数执行,直到下一个 yieldreturn 或结束。

  • 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 generator return 一个值时,该值会作为 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 用于异步迭代流。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐