目标:不仅会"用",还能"设计、调试、扩展、优化"。文内包含从零手写、生成器、惰性管道、异步流、资源管理、常见坑、性能建议、练习清单等。
1. 核心协议
- 可迭代协议 (Iterable) :对象实现
obj[Symbol.iterator](),返回一个迭代器。 - 迭代器协议 (Iterator) :返回值具备
next()方法,每次next()返回{ value, done }。 - 消费方 :
for...of、展开...、数组/对象解构、Promise.all、new Map(iterable)、new Set(iterable)、Array.from等。 - 原生可迭代 :
Array、String、Map、Set、TypedArray、arguments、NodeList等。
js
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。
js
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)
场景:为自定义对象提供可迭代能力,并处理提前终止。
js
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。
js
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 表达式的结果"传回生成器内部。
js
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捕获;若未捕获则向外抛出。
js
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 值。
js
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* 应用:递归/扁平化/管道组合
js
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...of :
for...of自动反复next()直到done:true;break/throw/return会触发迭代器的return()。
5. 可迭代工具箱与常见 API
- 展开/解构:
[...iterable]、const [a, ...rest] = iterable - 集合转换:
Array.from(iterable)、new Map(iterable)、new Set(iterable) - Promise 组合:
Promise.all(iterable)、Promise.allSettled、Promise.race(需可迭代)
js
const set = new Set([1, 2, 3]);
const arr = [...set]; // [1,2,3]
const [first, ...rest] = set; // first=1, rest=[2,3]
6. 自定义数据结构:可迭代的 Deque(类 + 私有字段)
js
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
用生成器实现"按需取值"的流式组合。
js
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.asyncIterator,next() 返回 Promise,或用 async function*。
js
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"的情况:
js
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() 保障资源释放。
js
function* readChunks(reader) {
try {
while (true) {
const chunk = reader.read();
if (!chunk) break;
yield chunk;
}
} finally {
reader.close(); // 即便 break/throw 也会执行
}
}
在异步生成器中同理使用 try/finally:
js
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...of中break/throw却未清理资源:实现return()或在生成器用finally。 for...of误用在异步迭代器:应改for await...of。- 隐式耗尽:
[...iter]会一次性拉平,若是大数据/无限序列会卡死或 OOM;改用惰性消费。 - 顺序期待:
Set/Map保持插入顺序;普通对象属性遍历顺序有规则但不属"可迭代"。
调试技巧:
js
const it = someIterable[Symbol.iterator]();
console.log(it.next(), it.next()); // 手动探查序列
11. 性能与工程化建议
- 惰性优先:未知大小或可能无限的来源用生成器/异步生成器。
- 避免重复遍历 :对昂贵来源(IO/计算)避免多次消费,可缓存结果或暴露
toArray()。 - 批量/背压 :异步流中可结合
take、chunk、throttle控制节奏。 - 类型提示 :在 TS 中为迭代器声明泛型,避免
any扩散。 - 组合优先:map/filter/take/drop/flatMap/zip 等算子小而精,利于单测和重用。
- 清理保证 :生成器里用
try/finally;显式实现return()以防资源泄漏。
12. 典型模式示例
12.1 管道式数据流
js
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 限流
js
function* naturals() { let i = 1; while (true) yield i++; }
console.log([...take(naturals(), 5)]); // [1,2,3,4,5]
12.3 异步分页封装
js
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)
js
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 提供统一、可组合的访问抽象;生成器/异步生成器进一步让"惰性、流式、可中断"变得自然。工程落地时,请同时关注资源释放、背压、可测试性与性能可观测性,把迭代封装成可靠的基础设施。