目标:不仅会“用”,还能“设计、调试、扩展、优化”。文内包含从零手写、生成器、惰性管道、异步流、资源管理、常见坑、性能建议、练习清单等。
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等。
constarr=[10,20];constit=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。
constarr=[3,6,9];for(constvofarr)console.log('of =>',v);// 3 6 9for(constkinarr)console.log('in =>',k);// 0 1 23. 从零手写同步迭代器(含 return/throw)
场景:为自定义对象提供可迭代能力,并处理提前终止。
constcounter={current:1,max:3,[Symbol.iterator](){constself=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(constnofcounter){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(leti=start;i<=end;i+=step)yieldi;// yield 产出,并“暂停”}constit=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(){constname=yield'你是谁?';constlang=yield`你好,${name},你用什么语言?`;return`${name}使用${lang}`;}constg=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{yield1;yield2;}finally{console.log('清理资源');}}constit2=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(){yield1;yield2;return9;// 会被 yield* 捕获}function*parent(){constret=yield*sub();// 产出 1、2,并获得 ret=9yieldret;// 再产出 return 值}console.log([...parent()]);// [1, 2, 9]yield* 应用:递归/扁平化/管道组合
function*flatten(tree){for(constnodeoftree){if(Array.isArray(node))yield*flatten(node);// 递归委托elseyieldnode;}}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(需可迭代)
constset=newSet([1,2,3]);constarr=[...set];// [1,2,3]const[first,...rest]=set;// first=1, rest=[2,3]6. 自定义数据结构:可迭代的 Deque(类 + 私有字段)
classDeque{#data=[];pushFront(x){this.#data.unshift(x);}pushBack(x){this.#data.push(x);}popFront(){returnthis.#data.shift();}popBack(){returnthis.#data.pop();}getsize(){returnthis.#data.length;}[Symbol.iterator](){letidx=0;return{next:()=>idx<this.#data.length?{value:this.#data[idx++],done:false}:{value:undefined,done:true},return(){return{done:true};},};}}constdq=newDeque();dq.pushBack(10);dq.pushFront(5);dq.pushBack(20);for(constvofdq)console.log(v);// 5 10 20设计建议:
- 迭代期间若会修改内部存储,需明确顺序定义与终止条件(如记录快照或用生成器惰性遍历)。
- 大数据/潜在无限序列优先用生成器,避免一次性展开耗内存。
7. 惰性管道:map / filter / take / drop
用生成器实现“按需取值”的流式组合。
function*map(iterable,fn){for(constxofiterable)yieldfn(x);}function*filter(iterable,pred){for(constxofiterable)if(pred(x))yieldx;}function*take(iterable,n){if(n<=0)return;leti=0;for(constxofiterable){yieldx;if(++i>=n)break;}}function*drop(iterable,n){leti=0;for(constxofiterable)if(i++>=n)yieldx;}constsrc=[1,2,3,4,5,6];constpipeline=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*。
constasyncCounter={current:1,max:3,async*[Symbol.asyncIterator](){while(this.current<=this.max){awaitnewPromise(r=>setTimeout(r,100));yieldthis.current++;}},};(async()=>{forawait(constnofasyncCounter)console.log(n);})();典型场景:分页 API、网络流(ReadableStream)、文件流、数据库游标、消息队列。
同步可迭代 + Promise 元素
for await...of也能遍历“同步可迭代且元素为 Promise”的情况:
constxs=[1,2,3].map(v=>Promise.resolve(v*10));(async()=>{forawait(constvofxs)console.log(v);// 10 20 30})();9. 资源管理与提前终止
在生成器中用try/finally+return()保障资源释放。
function*readChunks(reader){try{while(true){constchunk=reader.read();if(!chunk)break;yieldchunk;}}finally{reader.close();// 即便 break/throw 也会执行}}在异步生成器中同理使用try/finally:
asyncfunction*streamLines(stream){try{forawait(constlineofstream)yieldline;}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保持插入顺序;普通对象属性遍历顺序有规则但不属“可迭代”。
调试技巧:
constit=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 管道式数据流
function*flatMap(iterable,fn){for(constxofiterable){constres=fn(x);if(Symbol.iteratorinObject(res))yield*res;elseyieldres;}}constwords=['hi','js'];constchars=flatMap(words,w=>w.split(''));console.log([...chars]);// ['h','i','j','s']12.2 无限序列 + take 限流
function*naturals(){leti=1;while(true)yieldi++;}console.log([...take(naturals(),5)]);// [1,2,3,4,5]12.3 异步分页封装
asyncfunction*fetchPages(fetchPage){letpage=1;while(true){constdata=awaitfetchPage(page);if(!data.length)break;yielddata;page+=1;}}(async()=>{forawait(constpageoffetchPages(p=>api.list({page:p}))){console.log('page size',page.length);}})();12.4 具备回收的文件读取(Node)
constfs=require('fs');asyncfunction*readLines(path){conststream=fs.createReadStream(path,'utf8');try{forawait(constchunkofstream)yieldchunk;}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 提供统一、可组合的访问抽象;生成器/异步生成器进一步让“惰性、流式、可中断”变得自然。工程落地时,请同时关注资源释放、背压、可测试性与性能可观测性,把迭代封装成可靠的基础设施。