跳到正文
前端知识库
JavaScript

29. 说说你对Iterator和Generator的理解?

理解迭代协议、Generator 的暂停与恢复,并实现正确传播结果和异常的 Promise 自动执行器。

约 8 分钟JavaScript · Iterator · Generator · Promise · 异步 · 面试

29. 说说你对Iterator和Generator的理解?

问题解析

Iterator(迭代器)和 Generator(生成器)是 ES6 引入的重要特性,它们提供了一种更优雅的方式来处理数据集合和异步编程。面试中主要考察它们的基本概念、实现方式、使用场景以及相互关系。

核心概念

1. Iterator(迭代器)

Iterator 是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署了 Iterator 接口,就可以完成遍历操作。

// 迭代器的基本结构
const iterator = {
  next() {
    return {
      value: /* 当前值 */,
      done: /* 是否遍历完成 */
    };
  }
};

// 示例
function createIterator(array) {
  let index = 0;
  return {
    next() {
      if (index < array.length) {
        return { value: array[index++], done: false };
      }
      return { value: undefined, done: true };
    }
  };
}

const iter = createIterator([1, 2, 3]);
console.log(iter.next()); // { value: 1, done: false }
console.log(iter.next()); // { value: 2, done: false }
console.log(iter.next()); // { value: 3, done: false }
console.log(iter.next()); // { value: undefined, done: true }

2. 可迭代协议(Iterable Protocol)

一个对象要成为可迭代对象,必须实现 @@iterator 方法(即 Symbol.iterator)。

// 使对象可迭代
const iterable = {
  data: [1, 2, 3],
  [Symbol.iterator]() {
    let index = 0;
    const data = this.data;
    return {
      next() {
        if (index < data.length) {
          return { value: data[index++], done: false };
        }
        return { done: true };
      }
    };
  }
};

// 使用 for...of 遍历
for (const item of iterable) {
  console.log(item); // 1, 2, 3
}

// 使用展开运算符
console.log([...iterable]); // [1, 2, 3]

3. Generator(生成器)

Generator 是一种特殊的函数,可以暂停执行和恢复执行,使用 function* 语法定义。

function* generatorFunction() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = generatorFunction();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

详细解答

1. 原生可迭代对象

// JavaScript 内置的可迭代对象

// 1. Array
const arr = [1, 2, 3];
for (const item of arr) console.log(item);

// 2. String
const str = 'hello';
for (const char of str) console.log(char);

// 3. Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) console.log(key, value);

// 4. Set
const set = new Set([1, 2, 3]);
for (const item of set) console.log(item);

// 5. TypedArray
const typedArray = new Uint8Array([1, 2, 3]);
for (const item of typedArray) console.log(item);

// 6. arguments 对象
function test() {
  for (const arg of arguments) console.log(arg);
}
test(1, 2, 3);

// 7. NodeList
// const nodes = document.querySelectorAll('div');
// for (const node of nodes) console.log(node);

2. Generator 的详细用法

// 1. 基本用法
function* simpleGenerator() {
  yield 'first';
  yield 'second';
  return 'done';
}

const gen = simpleGenerator();
console.log(gen.next()); // { value: 'first', done: false }
console.log(gen.next()); // { value: 'second', done: false }
console.log(gen.next()); // { value: 'done', done: true }
console.log(gen.next()); // { value: undefined, done: true }

// 2. yield 表达式
function* yieldExpression() {
  const received = yield 'sent'; // yield 可以接收值
  console.log('Received:', received);
  yield 'next';
}

const gen2 = yieldExpression();
console.log(gen2.next());        // { value: 'sent', done: false }
console.log(gen2.next('value')); // 传入值给上一个 yield
// 输出: Received: value
// 返回: { value: 'next', done: false }

// 3. 在 Generator 中使用 return
function* withReturn() {
  yield 1;
  yield 2;
  return 3;  // return 会设置 done 为 true
  yield 4;   // 不会执行
}

const gen3 = withReturn();
console.log([...gen3]); // [1, 2] (return 的值不会被展开)

// 4. throw 方法
function* errorGenerator() {
  try {
    yield 1;
    yield 2;
  } catch (e) {
    console.log('Caught:', e);
    yield 'recovered';
  }
}

const gen4 = errorGenerator();
console.log(gen4.next()); // { value: 1, done: false }
console.log(gen4.throw(new Error('Test error')));
// 输出: Caught: Error: Test error
// 返回: { value: 'recovered', done: false }

3. Generator 与 Iterator 的关系

// Generator 自动实现 Iterator 接口
function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();

// Generator 对象具有 Symbol.iterator 方法
console.log(typeof gen[Symbol.iterator]); // 'function'

// 并且返回自身
console.log(gen[Symbol.iterator]() === gen); // true

// 因此可以使用 for...of
for (const item of gen) {
  console.log(item); // 1, 2, 3
}

深入理解

1. 自定义迭代器

// 实现一个 Range 类
class Range {
  constructor(start, end, step = 1) {
    this.start = start;
    this.end = end;
    this.step = step;
  }

  [Symbol.iterator]() {
    let current = this.start;
    const { end, step } = this;

    return {
      next() {
        if (current <= end) {
          const value = current;
          current += step;
          return { value, done: false };
        }
        return { done: true };
      }
    };
  }
}

const range = new Range(1, 10, 2);
console.log([...range]); // [1, 3, 5, 7, 9]

// 使用 Generator 简化
class RangeWithGenerator {
  constructor(start, end, step = 1) {
    this.start = start;
    this.end = end;
    this.step = step;
  }

  *[Symbol.iterator]() {
    for (let i = this.start; i <= this.end; i += this.step) {
      yield i;
    }
  }
}

const range2 = new RangeWithGenerator(1, 5);
console.log([...range2]); // [1, 2, 3, 4, 5]

2. 无限序列

// 使用 Generator 创建无限序列
function* infiniteSequence() {
  let i = 0;
  while (true) {
    yield i++;
  }
}

const infinite = infiniteSequence();
console.log(infinite.next().value); // 0
console.log(infinite.next().value); // 1
console.log(infinite.next().value); // 2
// ...

// 斐波那契数列
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5

// 获取前 n 个斐波那契数
function take(n, iterable) {
  const result = [];
  const iterator = iterable[Symbol.iterator]();
  for (let i = 0; i < n; i++) {
    const { value, done } = iterator.next();
    if (done) break;
    result.push(value);
  }
  return result;
}

console.log(take(10, fibonacci()));
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

3. 异步迭代器

// 异步迭代器
const asyncIterable = {
  [Symbol.asyncIterator]: async function* () {
    yield new Promise(resolve => setTimeout(() => resolve(1), 1000));
    yield new Promise(resolve => setTimeout(() => resolve(2), 1000));
    yield new Promise(resolve => setTimeout(() => resolve(3), 1000));
  }
};

// 使用 for await...of
async function consumeAsync() {
  for await (const item of asyncIterable) {
    console.log(item); // 1, 2, 3 (每秒一个)
  }
}

// 异步 Generator
async function* asyncGenerator() {
  const urls = [
    'https://api.example.com/data1',
    'https://api.example.com/data2',
    'https://api.example.com/data3'
  ];

  for (const url of urls) {
    const response = await fetch(url);
    yield response.json();
  }
}

// 使用
async function fetchAll() {
  for await (const data of asyncGenerator()) {
    console.log(data);
  }
}

最佳实践

1. 迭代器模式应用

// 实现一个二叉树的中序遍历迭代器
class TreeNode {
  constructor(value) {
    this.value = value;
    this.left = null;
    this.right = null;
  }
}

class BinaryTree {
  constructor(root) {
    this.root = root;
  }

  // 中序遍历迭代器
  *[Symbol.iterator]() {
    function* inorder(node) {
      if (node) {
        yield* inorder(node.left);
        yield node.value;
        yield* inorder(node.right);
      }
    }
    yield* inorder(this.root);
  }
}

// 构建树
const root = new TreeNode(4);
root.left = new TreeNode(2);
root.right = new TreeNode(6);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(3);

const tree = new BinaryTree(root);
console.log([...tree]); // [1, 2, 3, 4, 6]

2. 生成器实现控制流

// 使用 Generator 实现任务队列
function* taskQueue() {
  const tasks = [];

  while (true) {
    const task = yield tasks.length;
    if (task) {
      tasks.push(task);
    }
  }
}

const queue = taskQueue();
queue.next(); // 初始化

console.log(queue.next(() => console.log('Task 1')).value); // 1
console.log(queue.next(() => console.log('Task 2')).value); // 2

// 执行所有任务
tasks.forEach(task => task());

// 状态机实现
function* stateMachine() {
  let state = 'IDLE';

  while (true) {
    const event = yield state;

    switch (state) {
      case 'IDLE':
        if (event === 'START') state = 'RUNNING';
        break;
      case 'RUNNING':
        if (event === 'PAUSE') state = 'PAUSED';
        if (event === 'STOP') state = 'STOPPED';
        break;
      case 'PAUSED':
        if (event === 'RESUME') state = 'RUNNING';
        if (event === 'STOP') state = 'STOPPED';
        break;
      case 'STOPPED':
        if (event === 'RESET') state = 'IDLE';
        break;
    }
  }
}

const machine = stateMachine();
machine.next(); // 初始化

console.log(machine.next('START').value);  // 'RUNNING'
console.log(machine.next('PAUSE').value);  // 'PAUSED'
console.log(machine.next('RESUME').value); // 'RUNNING'
console.log(machine.next('STOP').value);   // 'STOPPED'

3. 惰性计算

// 惰性求值的管道操作
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) break;
    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
);

console.log([...result]); // [4, 16, 36]
// 实际只计算了前 6 个元素,而不是全部 10 个

4. 协程与异步控制

Generator 只提供暂停和恢复的控制能力;调用 next() 本身仍同步执行到下一个 yield 或结束。yield promise 会把 Promise 交给外部,既不会等待它完成,也不会自行恢复。自动执行器负责等待,再把结果或异常送回暂停点。

next、throw 与 return 的职责

操作 暂停点恢复方式 注意事项
next(value) 让当前 yield 表达式取得 value,继续执行 第一次调用只负责启动,传参会被忽略
throw(error) 像在暂停的 yield 处抛出异常 内部 try/catch 可以恢复;未捕获时向调用方抛出,生成器关闭
return(value) 请求提前返回,并执行途经的 finally finally 内仍可能 yield,此时 done 仍为 false,要继续驱动才能完成清理

throw() 的恢复可能经过 catch 后停在新 yield,并非额外调用了一次 next()。在首次 next() 之前调用 throw(),函数体尚未进入,内部 catch 无法接住它。

function* resourceFlow() {
  try {
    yield 'working';
  } finally {
    yield 'cleanup';
  }
}

const resource = resourceFlow();
resource.next();          // { value: 'working', done: false }
resource.return('stop');  // { value: 'cleanup', done: false }
resource.next();          // { value: 'stop', done: true }

一个可返回结果并传播异常的自动执行器

下面只驱动同步 Generator,接受普通值、Promise 和 thenable;每次 yield 的结果都会经过 Promise 归一化。它提供理解 async/await 控制流的模型,不是原生语法的完整实现。

function run(generator, ...args) {
  return new Promise((resolve, reject) => {
    // 工厂调用若同步抛错,Promise 构造器会把它转成拒绝。
    const iterator = generator(...args);

    function step(method, input) {
      try {
        const { value, done } = iterator[method](input);
        if (done) {
          resolve(value); // 同样吸收 return 返回的 Promise/thenable。
          return;
        }

        Promise.resolve(value).then(
          result => step('next', result),
          error => step('throw', error)
        );
      } catch (error) {
        // 包含首次 next、后续 next/throw 的同步异常。
        reject(error);
      }
    }

    step('next');
  });
}

function* calculate() {
  const base = yield 2;
  const extra = yield { then(resolve) { resolve(3); } };

  try {
    yield Promise.reject(new Error('暂时不可用'));
  } catch (error) {
    return Promise.resolve(`${base + extra}: ${error.message}`);
  }
}

run(calculate).then(console.log); // '5: 暂时不可用'

成功结果只走 next,当前产出值的拒绝只走 throw;生成器自身未捕获的异常直接拒绝最外层 Promise。不要写成递归的 .then(继续执行).catch(回灌异常):这个 catch 还会接住后续递归流程的拒绝,让同一个错误沿此前多层 catch 再次进入已经结束的生成器。

调用者必须拿到 run() 返回的 Promise,才能消费最终 return 值或处理失败。可以验证:普通值和 thenable 能继续执行;拒绝可被生成器内部捕获;首次执行、恢复执行或恢复时的 catch 抛错都只使外层拒绝;return Promise.reject(...) 也会向外拒绝。

此执行器没有取消接口,也不接受回调式 thunk 或异步 Generator。return() 只请求结束迭代,本身不会取消已发出的网络请求;需要取消时还应给底层操作传递 AbortSignal,并决定如何继续驱动 finally 中的清理。原生异步函数的区别见 Generator 与 async/await 的关系。

面试要点

  1. Iterator 接口:包含 next() 方法,返回 { value, done }(深入阅读:1. Iterator(迭代器))
  2. 可迭代协议:实现 [Symbol.iterator] 方法(深入阅读:2. 可迭代协议(Iterable Protocol))
  3. Generator:使用 function* 定义,使用 yield 暂停执行(深入阅读:3. Generator(生成器))
  4. 双向通信:通过 next(value) 向 Generator 传值(深入阅读:2. Generator 的详细用法)
  5. 应用场景:惰性计算、无限序列、异步流程控制、状态机(深入阅读:3. 惰性计算)

常见面试题

// 面试题 1:实现一个迭代器,遍历对象的深度属性
function* deepIterate(obj) {
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key];
      if (typeof value === 'object' && value !== null) {
        yield* deepIterate(value);
      } else {
        yield { key, value };
      }
    }
  }
}

const nested = {
  a: 1,
  b: { c: 2, d: { e: 3 } },
  f: 4
};

for (const item of deepIterate(nested)) {
  console.log(item);
}
// { key: 'a', value: 1 }
// { key: 'c', value: 2 }
// { key: 'e', value: 3 }
// { key: 'f', value: 4 }

// 面试题 2:Generator 实现数组扁平化
function* flatten(array) {
  for (const item of array) {
    if (Array.isArray(item)) {
      yield* flatten(item);
    } else {
      yield item;
    }
  }
}

const nestedArray = [1, [2, [3, 4], 5], 6];
console.log([...flatten(nestedArray)]); // [1, 2, 3, 4, 5, 6]

// 面试题 3:使用 Generator 实现 sleep 函数
function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function* workflow() {
  console.log('Start');
  yield sleep(1000);
  console.log('After 1s');
  yield sleep(2000);
  console.log('After 2s');
  return 'Done';
}

// 执行
// run(workflow).then(result => console.log(result));

// 面试题 4:实现一个可迭代对象的组合
function* combine(...iterables) {
  for (const iterable of iterables) {
    yield* iterable;
  }
}

const a = [1, 2, 3];
const b = new Set([4, 5, 6]);
const c = 'abc';

console.log([...combine(a, b, c)]);
// [1, 2, 3, 4, 5, 6, 'a', 'b', 'c']

// 面试题 5:Generator 和 Promise 的区别
// Generator:
// - 同步写法处理异步
// - 可以暂停和恢复
// - 需要执行器配合
// - 适合复杂流程控制

// Promise:
// - 表示操作的最终结果;executor 同步运行,then 回调异步调度
// - 链式调用
// - 内置执行机制
// - 适合简单异步操作

深入阅读:1. 迭代器模式应用、2. Generator 的详细用法、3. Generator 与 Iterator 的关系、1. 原生可迭代对象

来源与取舍

  • 《JS异步编程.pdf》(FE-005)第 7-9 页:复用已有迭代与双向传值基础,将手动驱动到自动执行的内容并入「协程与异步控制」。
  • 修正来源执行器没有返回执行 Promise、首次同步异常外漏,以及递归 .catch() 重复回灌异常的问题;补充 return() 与清理的边界,作为执行器面试追问。
  • ES6 专题通过链接复用本篇执行器,不维护第二套实现。