javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > js迭代器与可迭代对象

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

作者:珑墨

迭代器是一个对象,它提供了一种标准的方式来遍历集合中的元素,这篇文章主要介绍了js迭代器与可迭代对象终极用法的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

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

1. 核心协议

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 对比

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()
}

要点:

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

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

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

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:主动收尾与异常注入

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 生成器的执行特性与调试要点

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

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. 常见坑排查表(含错误示例)

调试技巧:

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

11. 性能与工程化建议

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 精要

15. 结语

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

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

您可能感兴趣的文章:
阅读全文