返回首页

你是怎么理解ES6中Generator的?使用场景?

问题解析

Generator是ES6引入的一种特殊的函数,它是异步编程的重要解决方案之一。这个问题考察对Generator函数的本质理解(状态机、迭代器)、yield表达式的工作机制、与Promise和async/await的关系,以及实际应用场景。

核心概念

1. Generator函数的本质

  • 状态机:封装了多个内部状态的函数
  • 迭代器生成器:返回符合Iterator接口的对象
  • 可暂停执行:通过yield暂停,通过next()恢复

2. 核心特征

  • function* 声明语法
  • yield 表达式暂停执行并返回数据
  • next() 方法恢复执行并可传入参数
  • 返回 { value, done } 对象

3. Generator与异步编程

  • 提供同步化的异步代码书写方式
  • 是async/await的实现基础
  • 与Promise配合实现流程控制

详细解答

Generator函数基本语法

// 1. 声明方式
function* gen() {
  yield 1;
  yield 2;
  return 3;
}

// 2. 调用返回迭代器对象,不立即执行
const g = gen();
console.log(g); // Generator { }

// 3. next()方法执行到下一个yield
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
console.log(g.next()); // { value: undefined, done: true }

yield表达式详解

function* gen() {
  yield 'first';
  console.log('继续执行');
  yield 'second';
  console.log('再次继续');
  yield 'third';
}

const g = gen();
g.next(); // 输出: 无,返回 { value: 'first', done: false }
// 暂停在第一个yield后
g.next(); // 输出: '继续执行',返回 { value: 'second', done: false }
g.next(); // 输出: '再次继续',返回 { value: 'third', done: false }
g.next(); // 返回 { value: undefined, done: true }

// yield的返回值
function* genWithReturn() {
  const received = yield 'sent';
  console.log('received:', received);
  yield received;
}

const g2 = genWithReturn();
g2.next();           // { value: 'sent', done: false }
g2.next('hello');    // 输出: 'received: hello',返回 { value: 'hello', done: false }

yield接收next()传入的参数

function* gen() {
  const a = yield '第一步';
  console.log('a =', a);  // 11

  const b = yield '第二步';
  console.log('b =', b);  // 22

  const c = yield '第三步';
  console.log('c =', c);  // 33

  return a + b + c;
}

const g = gen();

// 第一个next()的传参会被忽略,因为用于启动生成器
console.log(g.next());        // { value: '第一步', done: false }
console.log(g.next(11));      // { value: '第二步', done: false }
console.log(g.next(22));      // { value: '第三步', done: false }
console.log(g.next(33));      // { value: 66, done: true }

for...of遍历Generator

function* gen() {
  yield 1;
  yield 2;
  yield 3;
  return 4; // for...of不会遍历return的值
}

for (const value of gen()) {
  console.log(value); // 1, 2, 3
}

// 使用展开运算符
const arr = [...gen()]; // [1, 2, 3]

// 解构赋值
const [a, b, c] = gen(); // a=1, b=2, c=3

为对象部署Iterator接口

// 使用Generator为对象添加遍历能力
const obj = {
  data: ['a', 'b', 'c'],
  *[Symbol.iterator]() {
    for (const item of this.data) {
      yield item;
    }
  }
};

for (const item of obj) {
  console.log(item); // 'a', 'b', 'c'
}

// 或者遍历对象的属性
const obj2 = {
  name: 'Tom',
  age: 25,
  job: 'developer',
  *[Symbol.iterator]() {
    const keys = Object.keys(this);
    for (const key of keys) {
      yield [key, this[key]];
    }
  }
};

for (const [key, value] of obj2) {
  console.log(key, value);
}

Generator与Promise结合

// 自动执行Generator的辅助函数
function run(generator) {
  const gen = generator();

  function next(data) {
    const result = gen.next(data);
    if (result.done) return result.value;

    result.value.then(data => {
      next(data);
    });
  }

  next();
}

// 使用
function fetchUser(id) {
  return fetch(`/api/users/${id}`).then(r => r.json());
}

function* fetchData() {
  try {
    const user = yield fetchUser(1);
    console.log('User:', user);

    const posts = yield fetch(`/api/posts?userId=${user.id}`).then(r => r.json());
    console.log('Posts:', posts);

    return posts;
  } catch (error) {
    console.error('Error:', error);
  }
}

run(fetchData);

Generator的错误处理

function* gen() {
  try {
    yield 1;
    yield 2;
    yield 3;
  } catch (e) {
    console.log('内部捕获:', e);
    yield '错误已处理';
  }
}

const g = gen();
g.next();        // { value: 1, done: false }
g.throw('错误'); // 内部捕获: 错误,返回 { value: '错误已处理', done: false }
g.next();        // { value: undefined, done: true }

// throw方法被捕获后,会自动执行一次next
function* gen2() {
  yield 1;
  yield 2;
}

const g2 = gen2();
g2.next();        // { value: 1, done: false }
try {
  g2.throw('错误'); // 未被捕获,抛出到外部
} catch (e) {
  console.log('外部捕获:', e);
}

深入理解

1. Generator的状态机本质

Generator函数可以看作是一个状态机,封装了多个内部状态:

function* stateMachine() {
  // 状态1
  yield 'STATE_1';

  // 状态2
  yield 'STATE_2';

  // 状态3
  yield 'STATE_3';

  // 最终状态
  return 'FINAL';
}

// 每次yield对应一个状态转换
// 这种特性使得Generator非常适合实现工作流、状态流转等场景

2. Generator与协程

Generator实现了JavaScript中的"半协程"(semi-coroutine):

// 协程特点:可以暂停和恢复,但只能由调用方恢复
// 对比线程:线程是抢占式多任务,协程是协作式多任务

function* taskA() {
  console.log('Task A - Step 1');
  yield; // 让出执行权
  console.log('Task A - Step 2');
  yield;
  console.log('Task A - Step 3');
}

function* taskB() {
  console.log('Task B - Step 1');
  yield;
  console.log('Task B - Step 2');
  yield;
  console.log('Task B - Step 3');
}

// 协作式调度
const a = taskA();
const b = taskB();

a.next(); // Task A - Step 1
b.next(); // Task B - Step 1
a.next(); // Task A - Step 2
b.next(); // Task B - Step 2
// ...

3. Generator与async/await的关系

async/await是Generator的语法糖:

// async/await写法
async function fetchData() {
  const user = await fetchUser(1);
  const posts = await fetchPosts(user.id);
  return posts;
}

// 等价于Generator写法
function* fetchDataGen() {
  const user = yield fetchUser(1);
  const posts = yield fetchPosts(user.id);
  return posts;
}

// async函数本质上就是自动执行的Generator
// async函数 = Generator + 自动执行器 + Promise

4. yield* 委托迭代

// yield* 将迭代委托给另一个Generator或可迭代对象

function* gen1() {
  yield 'a';
  yield 'b';
}

function* gen2() {
  yield 1;
  yield* gen1(); // 委托给gen1
  yield 2;
  yield* [3, 4, 5]; // 委托给数组
  yield 6;
}

const arr = [...gen2()]; // [1, 'a', 'b', 2, 3, 4, 5, 6]

// 获取被委托Generator的返回值
function* gen3() {
  yield 'start';
  const result = yield* gen4();
  console.log('gen4 returned:', result);
  yield 'end';
}

function* gen4() {
  yield 'inner';
  return 'done'; // 这个返回值会被yield*捕获
}

const g = gen3();
g.next(); // { value: 'start', done: false }
g.next(); // gen4 returned: done, { value: 'end', done: false }

5. Generator的内存特性

// Generator是惰性求值的,适合处理大数据

function* infiniteSequence() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

// 不会占用大量内存,按需生成
const seq = infiniteSequence();
console.log(seq.next().value); // 0
console.log(seq.next().value); // 1
console.log(seq.next().value); // 2
// ...

// 对比数组实现
function infiniteArray() {
  const arr = [];
  for (let i = 0; i < 1000000; i++) {
    arr.push(i);
  }
  return arr; // 内存占用大
}

最佳实践

1. 异步流程控制

// 使用co库或自行实现自动执行器
const co = require('co');

co(function* () {
  const result = yield Promise.resolve(true);
  return result;
}).then(value => {
  console.log(value);
}, err => {
  console.error(err.stack);
});

// 或者使用async/await(推荐)
async function modernAsync() {
  const result = await Promise.resolve(true);
  return result;
}

2. 数据流处理

// 使用Generator实现管道处理
function* map(iterable, fn) {
  for (const item of iterable) {
    yield fn(item);
  }
}

function* filter(iterable, predicate) {
  for (const item of iterable) {
    if (predicate(item)) {
      yield item;
    }
  }
}

function* take(iterable, n) {
  let count = 0;
  for (const item of iterable) {
    if (count >= n) return;
    yield item;
    count++;
  }
}

// 惰性求值的管道
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = [
  ...take(
    filter(
      map(numbers, x => x * x),
      x => x % 2 === 0
    ),
    3
  )
];
// [4, 16, 36]

3. 实现可迭代对象

// 为二叉树实现遍历
class BinaryTree {
  constructor(value, left = null, right = null) {
    this.value = value;
    this.left = left;
    this.right = right;
  }

  // 中序遍历
  *[Symbol.iterator]() {
    if (this.left) {
      yield* this.left;
    }
    yield this.value;
    if (this.right) {
      yield* this.right;
    }
  }

  // 先序遍历
  *preOrder() {
    yield this.value;
    if (this.left) {
      yield* this.left.preOrder();
    }
    if (this.right) {
      yield* this.right.preOrder();
    }
  }
}

const tree = new BinaryTree(
  4,
  new BinaryTree(2, new BinaryTree(1), new BinaryTree(3)),
  new BinaryTree(6, new BinaryTree(5), new BinaryTree(7))
);

console.log([...tree]); // [1, 2, 3, 4, 5, 6, 7]
console.log([...tree.preOrder()]); // [4, 2, 1, 3, 6, 5, 7]

面试要点

  1. Generator的本质

    • 状态机,封装多个内部状态
    • 返回Iterator对象
    • 可暂停和恢复执行的函数
  2. 核心语法

    • function* 声明
    • yield 暂停并返回数据
    • next() 恢复执行,可传参
    • 返回 { value, done } 对象
  3. next()传参机制

    • 第一个next()的参数会被忽略
    • 后续next(arg)的参数会成为上一个yield的返回值
  4. for...of与Generator

    • for...of会自动遍历Generator直到done为true
    • 不会遍历return的值
    • 可用于为对象部署Iterator接口
  5. 与Promise/async的关系

    • Generator是async/await的实现基础
    • async函数 = Generator + 自动执行器
    • 现代代码优先使用async/await
  6. yield*的作用

    • 委托迭代给另一个Generator或可迭代对象
    • 可以获取被委托Generator的return值
  7. 使用场景

    • 异步操作的同步化表达(已被async/await取代)
    • redux-saga等中间件
    • 为对象部署Iterator接口
    • 惰性求值、无限序列
    • 协程风格的任务调度
  8. 注意事项

    • Generator函数不能用作构造函数
    • throw()方法会抛出错误到Generator内部
    • return()方法可提前终止Generator