2026/6/10 19:54:49
网站建设
项目流程
网站建设免费ppt,沧州市有哪些网络公司,网站推广合同,洛阳网站建设汉狮报价JavaScript学习笔记#xff1a;15.迭代器与生成器
上一篇用类型数组搞定了二进制数据的“高效存储”#xff0c;这一篇咱们解锁JS遍历的“终极形态”——迭代器#xff08;Iterators#xff09;与生成器#xff08;Generators#xff09;。你肯定用过for循环遍历数组15.迭代器与生成器上一篇用类型数组搞定了二进制数据的“高效存储”这一篇咱们解锁JS遍历的“终极形态”——迭代器Iterators与生成器Generators。你肯定用过for循环遍历数组用for...of遍历Set但有没有想过为什么数组能直接用for...of普通对象却不行为什么有些遍历能“暂停”比如异步请求依次执行这些问题的答案都藏在迭代器和生成器里。简单说迭代器是“遍历说明书”——告诉程序如何一步步取出数据生成器是“智能导游”——不仅能按说明书带路还能随时暂停、接收指令调整路线。今天咱们就用“旅游”的生活化比喻把这对“遍历搭档”的原理、用法和实战价值彻底讲透让你写出更灵活、更优雅的遍历代码。一、先破案为什么需要迭代器普通循环不够用吗普通循环for、while就像“自己开车逛景区”——路线得自己规划停车点得自己记遇到复杂数据结构比如树、链表就手忙脚乱。咱们先看普通循环的三大痛点遍历逻辑不统一遍历数组要记索引i从0到length-1遍历Set用forEach遍历Map要forEach或entries()每种数据结构一套逻辑记起来麻烦无法暂停与恢复循环一旦启动就必须跑完想在遍历中等待异步任务比如遍历请求列表前一个请求完成再发下一个根本做不到普通对象不能直接遍历for...of能遍历数组/Set/Map却不能直接遍历普通对象得先转成Object.keys(obj)多此一举。而迭代器和生成器的出现就是为了解决这些问题统一遍历逻辑不管是数组、自定义数据结构还是树/链表都用for...of遍历不用记不同语法支持暂停恢复遍历过程中能暂停等待异步任务完成再继续完美适配异步场景让任意对象可遍历给普通对象加个“遍历说明书”就能直接用for...of遍历。二、迭代器遍历的“基础协议”——像台“自动售货机”迭代器的核心是“迭代器协议”——一个对象只要有next()方法且返回{ value: 下一个值, done: 是否结束 }它就是迭代器。就像自动售货机投币调用next()→ 出商品value→ 售罄done: true。1. 迭代器的核心规则必须有next()方法无参数或一个参数next()返回对象必须包含done布尔值可选包含value迭代器是“一次性消耗”的遍历到done: true后再调用next()永远返回{ done: true }。2. 手动实现迭代器体验“售货机”的工作原理咱们自定义一个“1~5的整数迭代器”手动实现迭代器协议理解底层逻辑// 自定义迭代器生成1~5的整数functioncreateNumberIterator(){letcurrent1;constmax5;// 返回迭代器对象符合迭代器协议return{next(){if(currentmax){return{value:current,done:false};}else{return{done:true};// 遍历结束可省略value}}};}// 使用迭代器constiteratorcreateNumberIterator();console.log(iterator.next());// { value: 1, done: false }console.log(iterator.next());// { value: 2, done: false }console.log(iterator.next());// { value: 3, done: false }console.log(iterator.next());// { value: 4, done: false }console.log(iterator.next());// { value: 5, done: false }console.log(iterator.next());// { done: true }console.log(iterator.next());// { done: true }已消耗永远返回结束这个例子能直观看到迭代器通过闭包维护current状态每次next()推进状态直到done: true。3. 迭代器的优势支持无限序列普通数组无法存储无限数据比如自然数序列但迭代器是“按需生成”的能轻松实现无限序列// 无限自然数迭代器functioncreateInfiniteIterator(){letcurrent1;return{next(){return{value:current,done:false};// 永远不结束}};}constinfiniteItcreateInfiniteIterator();console.log(infiniteIt.next().value);// 1console.log(infiniteIt.next().value);// 2console.log(infiniteIt.next().value);// 3// 想要多少要多少不占额外内存三、可迭代对象能被for...of遍历的“合格数据”迭代器是“售货机”但for...of不直接遍历迭代器而是遍历“可迭代对象”——即拥有[Symbol.iterator]()方法的对象。这个方法调用后返回迭代器相当于“售货机的说明书”for...of会自动按说明书获取迭代器调用next()直到done: true。1. 内置可迭代对象JS中数组、String、Set、Map、类型数组都是内置可迭代对象因为它们的原型上有[Symbol.iterator]()方法// 数组是可迭代对象constarr[1,2,3];constarrItarr[Symbol.iterator]();// 获取迭代器console.log(arrIt.next());// { value: 1, done: false }// for...of自动调用[Symbol.iterator]()遍历迭代器for(constitemofarr){console.log(item);// 1、2、3}2. 让普通对象变成可迭代对象普通对象没有[Symbol.iterator]()所以不能用for...of。咱们给它加个“说明书”让它变成可迭代对象constuser{name:张三,hobbies:[篮球,游戏,美食],// 实现[Symbol.iterator]()返回迭代器[Symbol.iterator](){letindex0;consthobbiesthis.hobbies;return{next(){if(indexhobbies.length){return{value:hobbies[index],done:false};}else{return{done:true};}}};}};// 现在user是可迭代对象能被for...of遍历for(consthobbyofuser){console.log(hobby);// 篮球、游戏、美食}// 也能使用展开语法consthobbyArr[...user];console.log(hobbyArr);// [篮球, 游戏, 美食]3. 关键区别迭代器 vs 可迭代对象特性迭代器可迭代对象核心标识有next()方法有[Symbol.iterator]()方法作用提供遍历的具体逻辑提供迭代器的“创建说明书”能否被for...of遍历不能能例子createNumberIterator()返回值数组、Set、自定义user对象四、生成器简化迭代器的“智能导游”手动实现迭代器需要维护状态比如current、index麻烦且容易出错。生成器Generator是JS提供的“捷径”——用function*定义的函数调用后返回生成器同时是迭代器可迭代对象yield关键字实现暂停自动维护状态让迭代器创建变得超简单。1. 生成器的核心语法函数定义function* 函数名()注意*暂停标识yield value返回value给next()暂停执行调用生成器函数返回生成器对象不是执行函数体生成器是迭代器有next()方法也有[Symbol.iterator]()返回自身。2. 用生成器简化迭代器一行顶十行之前的“1~5整数迭代器”用生成器实现只要3行// 生成器函数生成1~5的整数function*numberGenerator(){yield1;yield2;yield3;yield4;yield5;}// 调用生成器函数返回生成器迭代器constgeneratornumberGenerator();// 生成器是迭代器支持next()console.log(generator.next());// { value: 1, done: false }console.log(generator.next());// { value: 2, done: false }// 生成器是可迭代对象支持for...offor(constnumofnumberGenerator()){console.log(num);// 1、2、3、4、5}更简洁的写法用for...in或循环// 生成1~max的整数生成器function*rangeGenerator(start1,end,step1){for(letistart;iend;istep){yieldi;}}// 遍历1~10步长2for(constnumofrangeGenerator(1,10,2)){console.log(num);// 1、3、5、7、9}3. 生成器的暂停与恢复智能导游的“灵活路线”yield的核心是“暂停执行”next()的核心是“恢复执行到下一个yield”。这个特性让生成器能实现“非连续执行”比如斐波那契数列// 斐波那契数列生成器function*fibGenerator(maxInfinity){leta0,b1;while(bmax){yieldb;// 暂停返回b下次从这里继续[a,b][b,ab];}}// 遍历前5个斐波那契数constfibItfibGenerator();console.log(fibIt.next().value);// 1console.log(fibIt.next().value);// 1console.log(fibIt.next().value);// 2console.log(fibIt.next().value);// 3console.log(fibIt.next().value);// 5五、高级用法生成器的“进阶技能”1. next()传参暂停后调整状态next()可以传参数这个参数会成为上一个yield的返回值实现“暂停后给生成器传指令”// 带参数的生成器根据传入值调整步长function*adjustGenerator(start1){letstep1;while(true){// 接收next()传入的参数作为yield的返回值constnewStepyieldstart;// 如果传了新步长更新stepif(newStep)stepnewStep;startstep;}}constadjustItadjustGenerator(1);console.log(adjustIt.next().value);// 1第一次传参无效console.log(adjustIt.next(2).value);// 3步长改为212console.log(adjustIt.next(3).value);// 6步长改为333console.log(adjustIt.next(1).value);// 7步长改为1612. throw()暂停时抛出异常throw()方法给生成器抛出异常异常会在当前暂停的yield处抛出可在生成器内部捕获function*errorGenerator(){try{yield1;yield2;yield3;}catch(err){console.log(捕获异常,err.message);yield异常后继续执行;}}consterrIterrorGenerator();console.log(errIt.next().value);// 1errIt.throw(newError(手动抛出异常));// 捕获异常手动抛出异常console.log(errIt.next().value);// 异常后继续执行3. return()提前终止生成器return(value)让生成器立即返回{ value, done: true }后续next()都返回{ done: true }constgenrangeGenerator(1,5);console.log(gen.next().value);// 1console.log(gen.return(提前终止).value);// 提前终止console.log(gen.next().done);// true六、实战场景迭代器与生成器的“用武之地”1. 场景1遍历自定义数据结构树/链表迭代器适合遍历复杂数据结构比如二叉树的中序遍历// 二叉树节点classTreeNode{constructor(val){this.valval;this.leftnull;this.rightnull;}}// 二叉树中序遍历生成器function*inorderTraversal(root){if(root){yield*inorderTraversal(root.left);// 递归遍历左子树yieldroot.val;// 返回当前节点值yield*inorderTraversal(root.right);// 递归遍历右子树}}// 构建二叉树constrootnewTreeNode(1);root.rightnewTreeNode(2);root.right.leftnewTreeNode(3);// 遍历二叉树for(constvalofinorderTraversal(root)){console.log(val);// 1、3、2中序遍历结果}2. 场景2异步迭代依次执行异步任务生成器的暂停特性适合处理异步流程比如依次请求多个接口前一个成功再请求下一个// 模拟异步请求functionfetchData(url){returnnewPromise(resolve{setTimeout(()resolve(数据${url}),1000);});}// 异步生成器依次请求接口function*asyncGenerator(urls){for(consturlofurls){constdatayieldfetchData(url);// 暂停等待Promise完成yielddata;// 返回数据}}// 执行异步生成器asyncfunctionrunAsyncGenerator(){consturls[url1,url2,url3];constgenasyncGenerator(urls);letresultgen.next();while(!result.done){// 如果是Promise等待其完成constvalueawaitresult.value;console.log(value);resultgen.next();}}runAsyncGenerator();// 每隔1秒输出一个数据3. 场景3无限序列按需生成不占内存处理大数据量时生成器按需生成数据避免一次性加载所有数据导致内存溢出// 生成100万以内的偶数按需生成不占内存function*evenGenerator(max1000000){for(leti2;imax;i2){yieldi;}}// 遍历前10个偶数后面的不生成constevenItevenGenerator();for(leti0;i10;i){console.log(evenIt.next().value);// 2、4、6...20}七、避坑指南这些坑千万别踩迭代器是一次性的遍历到done: true后再调用next()也不会重置需重新创建迭代器生成器不能重复迭代一个生成器对象只能遍历一次再次遍历需重新调用生成器函数next()第一次传参无效第一次调用next()时生成器还没执行到任何yield传参不会被接收普通对象不是可迭代对象别直接用for...of遍历普通对象需手动实现[Symbol.iterator]()yield只能在生成器函数内使用普通函数不能用yield会报错。八、总结迭代器与生成器的核心价值迭代器定义了“统一的遍历协议”让不同数据结构的遍历逻辑标准化生成器简化了迭代器的创建提供了“暂停/恢复”的强大特性两者结合让JS的遍历能力从“手动开车”升级为“智能导游”。核心价值总结统一遍历逻辑for...of通吃所有可迭代对象不用记多种遍历语法支持复杂场景无限序列、异步迭代、自定义数据结构遍历普通循环做不到优化性能按需生成数据避免一次性加载大数据导致的内存压力。掌握它们你就能从容应对复杂的遍历需求写出更优雅、更高效的代码。