JavaScript 迭代器与生成器

一、迭代器(Iterator)

1. 迭代器协议

迭代器协议定义了如何顺序访问集合中的元素 的标准方式。一个对象要成为迭代器,必须实现 next() 方法,该方法返回包含两个属性的对象:

  • value:当前迭代的值
  • done:布尔值,表示迭代是否完成
javascript 复制代码
const simpleIterator = {
  data: [1, 2, 3],
  index: 0,
  next() {
    return this.index < this.data.length
      ? { value: this.data[this.index++], done: false }
      : { value: undefined, done: true };
  }
};

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

2. 可迭代协议

可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为 。要成为可迭代对象,必须实现 @@iterator 方法(通过 Symbol.iterator 访问):

javascript 复制代码
const iterableObject = {
  items: ['a', 'b', 'c'],
  [Symbol.iterator]() {
    let index = 0;
    return {
      next: () => {
        return index < this.items.length
          ? { value: this.items[index++], done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

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

3. 内置可迭代对象

JavaScript 中许多内置对象都是可迭代的:

  • Array
  • String
  • Map
  • Set
  • TypedArray
  • arguments 对象
  • DOM 集合(如 NodeList)
javascript 复制代码
// 字符串迭代
for (const char of 'hello') {
  console.log(char); // h, e, l, l, o
}

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

4. 迭代器的使用场景

  1. 解构赋值

    javascript 复制代码
    const [first, second] = [1, 2, 3];
    console.log(first, second); // 1 2
  2. 扩展运算符

    javascript 复制代码
    const arr = [...'hello'];
    console.log(arr); // ['h', 'e', 'l', 'l', 'o']
  3. for...of 循环

    javascript 复制代码
    const set = new Set([1, 2, 3]);
    for (const num of set) {
      console.log(num); // 1, 2, 3
    }
  4. 接受可迭代对象的 API

    javascript 复制代码
    new Map([[1, 'a'], [2, 'b']]);
    Promise.all(iterable);
    Array.from(iterable);

二、生成器(Generator)

1. 生成器函数

生成器函数是能暂停和恢复执行 的特殊函数,使用 function* 语法定义:

javascript 复制代码
function* simpleGenerator() {
  yield 1;
  yield 2;
  return 3;
}

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

2. 生成器特性

  1. yield 表达式

    • 暂停函数执行并返回一个值
    • 可以通过 next() 恢复执行
  2. 双向通信

    • next(value) 可以向生成器内部传递值
    • yield 可以接收外部传入的值
javascript 复制代码
function* twoWayGenerator() {
  const name = yield 'What is your name?';
  yield `Hello, ${name}!`;
}

const gen = twoWayGenerator();
console.log(gen.next().value); // "What is your name?"
console.log(gen.next('Alice').value); // "Hello, Alice!"
  1. 提前终止
    • generator.return():终止生成器
    • generator.throw():向生成器抛出错误
javascript 复制代码
function* gen() {
  try {
    yield 1;
    yield 2;
  } catch (e) {
    console.log('Error caught:', e);
  }
}

const g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.throw(new Error('Something went wrong'))); 
// "Error caught: Error: Something went wrong"
// { value: undefined, done: true }

3. 生成器作为迭代器

生成器对象也是迭代器,可以用于实现可迭代对象:

javascript 复制代码
const iterableObject = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};

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

4. 异步生成器

ES2018 引入了异步生成器,用于处理异步迭代:

javascript 复制代码
async function* asyncGenerator() {
  const data1 = await fetchData1();
  yield data1;
  const data2 = await fetchData2();
  yield data2;
}

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

三、迭代器与生成器的实际应用

1. 实现自定义数据结构迭代

javascript 复制代码
class TreeNode {
  constructor(value, left = null, right = null) {
    this.value = value;
    this.left = left;
    this.right = right;
  }

  *[Symbol.iterator]() {
    yield this.value;
    if (this.left) yield* this.left;
    if (this.right) yield* this.right;
  }
}

const tree = new TreeNode(1, 
  new TreeNode(2, new TreeNode(3)), 
  new TreeNode(4)
);

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

2. 无限序列

javascript 复制代码
function* fibonacci() {
  let [prev, curr] = [0, 1];
  while (true) {
    yield curr;
    [prev, curr] = [curr, prev + curr];
  }
}

const fib = fibonacci();
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
// 可以无限继续

3. 分页数据获取

javascript 复制代码
async function* paginatedData(url) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    if (data.length === 0) return;
    yield* data;
    page++;
  }
}

// 使用
(async () => {
  for await (const item of paginatedData('/api/data')) {
    console.log(item);
  }
})();

4. 状态机实现

javascript 复制代码
function* stateMachine() {
  let state = 'start';
  while (true) {
    const input = yield state;
    switch (state) {
      case 'start':
        state = input === 'next' ? 'middle' : 'error';
        break;
      case 'middle':
        state = input === 'next' ? 'end' : 'error';
        break;
      case 'end':
        return 'done';
      case 'error':
        throw new Error('Invalid state transition');
    }
  }
}

const sm = stateMachine();
console.log(sm.next().value);        // 'start'
console.log(sm.next('next').value);  // 'middle'
console.log(sm.next('next').value);  // 'end'
console.log(sm.next().done);         // true

四、迭代器与生成器的区别与联系

特性 迭代器 生成器
创建方式 手动实现 next() 方法 使用 function* 定义
状态管理 需要手动维护状态 自动维护暂停/恢复状态
语法复杂度 相对复杂 更简洁
双向通信 只能单向输出值 可通过 yield 和 next() 双向通信
实现可迭代对象 需要实现 Symbol.iterator 本身就是可迭代对象
异步支持 需要额外实现 原生支持异步生成器

五、最佳实践与注意事项

1. 迭代器最佳实践

  1. 实现完整的迭代器协议

    • 确保 next() 方法返回 { value, done } 对象
    • 迭代完成后继续调用应返回 { value: undefined, done: true }
  2. 考虑可重用性

    • 每次调用 [Symbol.iterator]() 应返回新的迭代器
    • 避免共享迭代器状态
  3. 处理清理逻辑

    • 如果迭代涉及资源(如文件句柄),实现 return() 方法进行清理

2. 生成器最佳实践

  1. 合理使用 yield

    • 避免在循环中无意义的 yield
    • 考虑 yield 的性能开销
  2. 错误处理

    • 使用 try-catch 包裹可能出错的 yield
    • 考虑实现 throw() 方法的处理逻辑
  3. 资源管理

    • 对于需要清理的资源,使用 try-finally

      javascript 复制代码
      function* resourceGenerator() {
        const resource = allocateResource();
        try {
          yield resource;
        } finally {
          resource.release();
        }
      }

3. 常见错误

  1. 忘记生成器是惰性的

    javascript 复制代码
    function* gen() {
      console.log('Start');
      yield;
      console.log('End');
    }
    
    // 错误:以为会立即执行
    const g = gen(); // 不会有输出
    g.next(); // 输出 "Start"
    g.next(); // 输出 "End"
  2. 误解 yield 的返回值

    javascript 复制代码
    function* gen() {
      const result = yield 'question'; // result 由 next() 的参数决定
      console.log(result);
    }
    
    const g = gen();
    g.next(); // { value: 'question', done: false }
    g.next('answer'); // 输出 "answer"
  3. 滥用无限生成器

    javascript 复制代码
    function* infinite() {
      while (true) yield Math.random();
    }
    
    // 危险:可能导致内存问题
    const arr = [...infinite()]; // 永远不会结束

六、现代 JavaScript 中的发展

1. 异步迭代协议 (ES2018)

异步迭代器协议与常规迭代器类似,但:

  • 使用 Symbol.asyncIterator 而非 Symbol.iterator
  • next() 返回 Promise 解析为 { value, done }
  • 使用 for await...of 循环
javascript 复制代码
const asyncIterable = {
  [Symbol.asyncIterator]: async function* () {
    yield 'hello';
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield 'world';
  }
};

(async () => {
  for await (const item of asyncIterable) {
    console.log(item); // 'hello', 1秒后 'world'
  }
})();

2. 生成器委托 (yield*)

yield* 表达式用于委托给另一个生成器或可迭代对象:

javascript 复制代码
function* gen1() {
  yield 2;
  yield 3;
}

function* gen2() {
  yield 1;
  yield* gen1(); // 委托给 gen1
  yield 4;
}

console.log([...gen2()]); // [1, 2, 3, 4]

3. 与 async/await 结合

生成器可以简化异步代码:

javascript 复制代码
function* asyncGenerator() {
  const result1 = yield fetchData1();
  const result2 = yield fetchData2(result1);
  return result2;
}

// 运行函数
function runGenerator(generator) {
  const gen = generator();
  
  function handle(result) {
    if (result.done) return Promise.resolve(result.value);
    return Promise.resolve(result.value)
      .then(res => handle(gen.next(res)))
      .catch(err => handle(gen.throw(err)));
  }
  
  return handle(gen.next());
}

// 使用
runGenerator(asyncGenerator)
  .then(finalResult => console.log(finalResult));

七、总结

迭代器和生成器是 JavaScript 强大的编程工具:

  1. 迭代器

    • 提供了统一的集合访问接口
    • 使自定义数据结构可迭代
    • 是许多语言特性(如 for...of、扩展运算符)的基础
  2. 生成器

    • 允许函数暂停和恢复执行
    • 简化迭代器创建
    • 实现协程和高级控制流
    • 为异步编程提供更直观的解决方案

掌握这些概念可以:

  • 编写更清晰、更灵活的代码
  • 实现复杂的数据处理流程
  • 创建自定义的可迭代数据结构
  • 更好地理解和利用现代 JavaScript 特性(如 async/await)
相关推荐
真的很上进2 分钟前
2025最全TS手写题之partial/Omit/Pick/Exclude/Readonly/Required
java·前端·vue.js·python·算法·react·html5
用户6945295521704 分钟前
国内开源版“Manus”——AiPy实测:让你的工作生活走上“智动”化
前端·后端
帅夫帅夫7 分钟前
一文手撕call、apply、bind
前端·javascript·面试
J船长9 分钟前
APK战争 diffoscope
前端
鱼樱前端22 分钟前
重度Cursor用户 最强 Cursor Rules 和 Cursor 配置 mcp 以及最佳实践配置方式
前端
曼陀罗24 分钟前
Path<T> 、 keyof T 什么情况下用合适
前端
锈儿海老师29 分钟前
AST 工具大PK!Biome 的 GritQL 插件 vs. ast-grep,谁是你的菜?
前端·javascript·eslint
飞龙AI31 分钟前
鸿蒙Next实现瀑布流布局
前端
令狐寻欢31 分钟前
JavaScript中 的 Object.defineProperty 和 defineProperties
javascript
快起来别睡了32 分钟前
代理模式:送花风波
前端·javascript·架构