js迭代器与可迭代对象终极用法详解

 更新时间:2026年03月03日 09:34:26   作者:珑墨  
迭代器是一个对象,它提供了一种标准的方式来遍历集合中的元素,这篇文章主要介绍了js迭代器与可迭代对象终极用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

目标:不仅会“用”,还能“设计、调试、扩展、优化”。文内包含从零手写、生成器、惰性管道、异步流、资源管理、常见坑、性能建议、练习清单等。

1. 核心协议

  • 可迭代协议 (Iterable):对象实现 obj[Symbol.iterator](),返回一个迭代器。
  • 迭代器协议 (Iterator):返回值具备 next() 方法,每次 next() 返回 { value, done }
  • 消费方for...of、展开 ...、数组/对象解构、Promise.allnew Map(iterable)new Set(iterable)Array.from 等。
  • 原生可迭代ArrayStringMapSetTypedArrayargumentsNodeList 等。
const arr = [10, 20];
const it = arr[Symbol.iterator](); // 拿到迭代器
console.log(it.next()); // { value: 10, done: false }
console.log(it.next()); // { value: 20, done: false }
console.log(it.next()); // { value: undefined, done: true }

2. for…of / for…in / for await…of 对比

  • for...of:遍历“值”,依赖可迭代协议,顺序稳定。
  • for...in:遍历“可枚举属性键”,含原型链可枚举属性,不需要可迭代。
  • for await...of:遍历“异步可迭代”或“值为 Promise 的可迭代”,逐个 await
const arr = [3, 6, 9];
for (const v of arr) console.log('of =>', v); // 3 6 9
for (const k in arr) console.log('in =>', k); // 0 1 2

3. 从零手写同步迭代器(含 return/throw)

场景:为自定义对象提供可迭代能力,并处理提前终止。

const counter = {
  current: 1,
  max: 3,
  [Symbol.iterator]() {
    const self = this;
    return {
      next() {
        if (self.current <= self.max) {
          return { value: self.current++, done: false };
        }
        return { value: undefined, done: true };
      },
      return() {
        console.log('迭代被提前终止,执行清理逻辑');
        return { value: undefined, done: true };
      },
      throw(err) {
        console.log('外部抛错被捕获:', err.message);
        return { value: undefined, done: true };
      },
    };
  },
};

for (const n of counter) {
  console.log(n);
  if (n === 2) break; // 触发 return()
}

要点:

  • Symbol.iterator 返回的对象必须实现 next()
  • done: true 视为终止;value 可省略。
  • return() 可用于 break/return/throw 时的清理;throw() 让外部异常传入迭代器。

4. 生成器 (Generator) 深潜:function* / yield / yield*

生成器函数(function* / async function*)执行后返回一个“生成器对象”,它同时是迭代器和可迭代对象。生成器以“暂停/恢复”的方式运行,内部编译成状态机。

4.1 生成器函数 vs 生成器对象

  • 生成器函数:写法 function* foo() { ... }const foo = function* () { ... };箭头函数不能写成生成器。
  • 生成器对象:调用生成器函数得到,如 const it = foo();它拥有 next/return/throw 并实现 Symbol.iterator
function* range(start, end, step = 1) {
  for (let i = start; i <= end; i += step) yield i; // yield 产出,并“暂停”
}
const it = range(1, 3);
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }

4.2 yield 的双向通信与状态机

next(value) 会把 value 作为“上一个 yield 表达式的结果”传回生成器内部。

function* dialog() {
  const name = yield '你是谁?';
  const lang = yield `你好,${name},你用什么语言?`;
  return `${name} 使用 ${lang}`;
}
const g = dialog();
console.log(g.next());           // { value: '你是谁?', done: false }
console.log(g.next('Alice'));    // { value: '你好,Alice,你用什么语言?', done: false }
console.log(g.next('JavaScript'));// { value: 'Alice 使用 JavaScript', done: true }

4.3 return / throw:主动收尾与异常注入

  • iter.return(value):立即终止,返回 { value, done: true },触发生成器内的 finally
  • iter.throw(err):将错误注入生成器,在内部可被 try/catch 捕获;若未捕获则向外抛出。
function* work() {
  try {
    yield 1;
    yield 2;
  } finally {
    console.log('清理资源');
  }
}
const it2 = work();
console.log(it2.next());      // { value:1, done:false }
console.log(it2.return(99));  // 清理资源 -> { value:99, done:true }

4.4 yield*:委托/扁平化子迭代器,并可接收子迭代器的 return

yield* otherIterable 把“迭代控制权”交给子迭代器,等价于逐个 for...of 产出其值。yield* 的结果是子迭代器的 return 值。

function* sub() {
  yield 1;
  yield 2;
  return 9; // 会被 yield* 捕获
}
function* parent() {
  const ret = yield* sub();   // 产出 1、2,并获得 ret=9
  yield ret;                  // 再产出 return 值
}
console.log([...parent()]); // [1, 2, 9]

yield* 应用:递归/扁平化/管道组合

function* flatten(tree) {
  for (const node of tree) {
    if (Array.isArray(node)) yield* flatten(node); // 递归委托
    else yield node;
  }
}
console.log([...flatten([1, [2, [3, 4]], 5])]); // [1,2,3,4,5]

4.5 生成器的执行特性与调试要点

  • 惰性:直到调用 next() 才会继续运行;适合大/无限序列。
  • 单次消费:同一个生成器对象不可复位,需重新创建。
  • 清理:在生成器内部用 try/finally;外部可以 return() 触发。
  • 不可用箭头函数:箭头语法不支持 function*,需常规函数写法。
  • 与 for…offor...of 自动反复 next() 直到 done:truebreak/throw/return 会触发迭代器的 return()

5. 可迭代工具箱与常见 API

  • 展开/解构:[...iterable]const [a, ...rest] = iterable
  • 集合转换:Array.from(iterable)new Map(iterable)new Set(iterable)
  • Promise 组合:Promise.all(iterable)Promise.allSettledPromise.race(需可迭代)
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1,2,3]
const [first, ...rest] = set; // first=1, rest=[2,3]

6. 自定义数据结构:可迭代的 Deque(类 + 私有字段)

class Deque {
  #data = [];
  pushFront(x) { this.#data.unshift(x); }
  pushBack(x) { this.#data.push(x); }
  popFront() { return this.#data.shift(); }
  popBack() { return this.#data.pop(); }
  get size() { return this.#data.length; }

  [Symbol.iterator]() {
    let idx = 0;
    return {
      next: () =>
        idx < this.#data.length
          ? { value: this.#data[idx++], done: false }
          : { value: undefined, done: true },
      return() { return { done: true }; },
    };
  }
}

const dq = new Deque();
dq.pushBack(10); dq.pushFront(5); dq.pushBack(20);
for (const v of dq) console.log(v); // 5 10 20

设计建议:

  • 迭代期间若会修改内部存储,需明确顺序定义与终止条件(如记录快照或用生成器惰性遍历)。
  • 大数据/潜在无限序列优先用生成器,避免一次性展开耗内存。

7. 惰性管道:map / filter / take / drop

用生成器实现“按需取值”的流式组合。

function* map(iterable, fn) {
  for (const x of iterable) yield fn(x);
}
function* filter(iterable, pred) {
  for (const x of iterable) if (pred(x)) yield x;
}
function* take(iterable, n) {
  if (n <= 0) return;
  let i = 0;
  for (const x of iterable) {
    yield x;
    if (++i >= n) break;
  }
}
function* drop(iterable, n) {
  let i = 0;
  for (const x of iterable) if (i++ >= n) yield x;
}

const src = [1, 2, 3, 4, 5, 6];
const pipeline = take(filter(map(src, x => x * 3), x => x % 2 === 0), 2);
console.log([...pipeline]); // [6, 12]

优势:逐元素计算,适合大数据、IO 流;可轻松扩展更多算子(zip、flatMap、chunk、uniq 等)。

8. 异步迭代器与 for await…of

异步可迭代实现 Symbol.asyncIteratornext() 返回 Promise,或用 async function*

const asyncCounter = {
  current: 1,
  max: 3,
  async *[Symbol.asyncIterator]() {
    while (this.current <= this.max) {
      await new Promise(r => setTimeout(r, 100));
      yield this.current++;
    }
  },
};

(async () => {
  for await (const n of asyncCounter) console.log(n);
})();

典型场景:分页 API、网络流(ReadableStream)、文件流、数据库游标、消息队列。

同步可迭代 + Promise 元素

for await...of 也能遍历“同步可迭代且元素为 Promise”的情况:

const xs = [1, 2, 3].map(v => Promise.resolve(v * 10));
(async () => {
  for await (const v of xs) console.log(v); // 10 20 30
})();

9. 资源管理与提前终止

在生成器中用 try/finally + return() 保障资源释放。

function* readChunks(reader) {
  try {
    while (true) {
      const chunk = reader.read();
      if (!chunk) break;
      yield chunk;
    }
  } finally {
    reader.close(); // 即便 break/throw 也会执行
  }
}

在异步生成器中同理使用 try/finally

async function* streamLines(stream) {
  try {
    for await (const line of stream) yield line;
  } finally {
    stream.destroy?.();
  }
}

10. 常见坑排查表(含错误示例)

  • TypeError: object is not iterable:缺少 Symbol.iterator 或拼写错误。
  • 迭代器复用:多数迭代器是一次性的,复用要重新获取 obj[Symbol.iterator]()
  • for...ofbreak/throw 却未清理资源:实现 return() 或在生成器用 finally
  • for...of 误用在异步迭代器:应改 for await...of
  • 隐式耗尽:[...iter] 会一次性拉平,若是大数据/无限序列会卡死或 OOM;改用惰性消费。
  • 顺序期待:Set/Map 保持插入顺序;普通对象属性遍历顺序有规则但不属“可迭代”。

调试技巧:

const it = someIterable[Symbol.iterator]();
console.log(it.next(), it.next()); // 手动探查序列

11. 性能与工程化建议

  • 惰性优先:未知大小或可能无限的来源用生成器/异步生成器。
  • 避免重复遍历:对昂贵来源(IO/计算)避免多次消费,可缓存结果或暴露 toArray()
  • 批量/背压:异步流中可结合 takechunkthrottle 控制节奏。
  • 类型提示:在 TS 中为迭代器声明泛型,避免 any 扩散。
  • 组合优先:map/filter/take/drop/flatMap/zip 等算子小而精,利于单测和重用。
  • 清理保证:生成器里用 try/finally;显式实现 return() 以防资源泄漏。

12. 典型模式示例

12.1 管道式数据流

function* flatMap(iterable, fn) {
  for (const x of iterable) {
    const res = fn(x);
    if (Symbol.iterator in Object(res)) yield* res;
    else yield res;
  }
}

const words = ['hi', 'js'];
const chars = flatMap(words, w => w.split(''));
console.log([...chars]); // ['h','i','j','s']

12.2 无限序列 + take 限流

function* naturals() { let i = 1; while (true) yield i++; }
console.log([...take(naturals(), 5)]); // [1,2,3,4,5]

12.3 异步分页封装

async function* fetchPages(fetchPage) {
  let page = 1;
  while (true) {
    const data = await fetchPage(page);
    if (!data.length) break;
    yield data;
    page += 1;
  }
}

(async () => {
  for await (const page of fetchPages(p => api.list({ page: p }))) {
    console.log('page size', page.length);
  }
})();

12.4 具备回收的文件读取(Node)

const fs = require('fs');
async function* readLines(path) {
  const stream = fs.createReadStream(path, 'utf8');
  try {
    for await (const chunk of stream) yield chunk;
  } finally {
    stream.close();
  }
}

13. FAQ 精要

  • 何时用生成器 vs 普通函数? 需要“逐步产出/惰性/可中断/可组合”时用生成器。
  • 迭代器能重置吗? 原生多数不可;若需可重复遍历,应在 Symbol.iterator 中返回“新的迭代器实例”。
  • 如何判断对象可迭代? obj != null && typeof obj[Symbol.iterator] === 'function'
  • async 迭代器如何并行? 迭代本身是串行消费;并行可在内部批量启动 Promise,再逐个 yield 结果(注意背压)。
  • 能否在生成器里用 await? 不能,改用 async function* 或在外层 for await...of

15. 结语

迭代器与可迭代协议为 JS 提供统一、可组合的访问抽象;生成器/异步生成器进一步让“惰性、流式、可中断”变得自然。工程落地时,请同时关注资源释放、背压、可测试性与性能可观测性,把迭代封装成可靠的基础设施。

到此这篇关于js迭代器与可迭代对象终极用法的文章就介绍到这了,更多相关js迭代器与可迭代对象内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Bootstrap 按钮样式与使用代码详解

    Bootstrap 按钮样式与使用代码详解

    这篇文章主要介绍了Bootstrap -- 按钮样式与使用,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-12-12
  • js性能优化技巧

    js性能优化技巧

    性能优化:简而言之,就是在不影响系统运行正确性的前提下,使之运行地更快,完成特定功能所需的时间更短,本篇文章给大家介绍js性能优化技巧,需要的朋友参考下
    2015-11-11
  • 微信小程序实现登录界面

    微信小程序实现登录界面

    这篇文章主要为大家详细介绍了微信小程序实现登录界面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • JavaScript 创建随机数和随机图片

    JavaScript 创建随机数和随机图片

    关于javascript随机数的,很早以前的文章了,不过内容还是不错的,如果想要更多的效果,可以去脚本之家搜下。
    2009-12-12
  • 10个基于浏览器的JavaScript调试工具分享

    10个基于浏览器的JavaScript调试工具分享

    调试Javascript可能是web开发中最让人郁闷的事情,这里是10款我们精选的基于浏览器的JS在线调试工具,感兴趣的朋友可以参考下,或许对你有所帮助
    2013-02-02
  • JavaScript遍历数组的三种方法map、forEach与filter实例详解

    JavaScript遍历数组的三种方法map、forEach与filter实例详解

    这篇文章主要介绍了JavaScript遍历数组的三种方法map、forEach与filter,结合实例形式详细分析了javascript针对数组遍历的map、forEach与filter三种方法相关操作技巧与注意事项,需要的朋友可以参考下
    2019-02-02
  • JS实现选项卡效果的代码实例

    JS实现选项卡效果的代码实例

    这篇文章主要介绍了JS选项卡效果,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • js实现筛选功能

    js实现筛选功能

    这篇文章主要为大家详细介绍了js实现筛选功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • 微信小程序MUI导航栏透明渐变功能示例(通过改变opacity实现)

    微信小程序MUI导航栏透明渐变功能示例(通过改变opacity实现)

    这篇文章主要介绍了微信小程序MUI导航栏透明渐变功能,结合实例形式分析了通过改变opacity实现透明度渐变功能相关操作技巧,需要的朋友可以参考下
    2019-01-01
  • uni-app多环境配置实现自动部署的方式详解

    uni-app多环境配置实现自动部署的方式详解

    前后端分离开发模式中,无论前后端都有可能区分不同的环境配置,下面这篇文章主要给大家介绍了关于uni-app多环境配置实现自动部署的相关资料,需要的朋友可以参考下
    2022-06-06

最新评论