news 2026/4/23 15:07:45

JavaScript学习笔记:15.迭代器与生成器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript学习笔记:15.迭代器与生成器

JavaScript学习笔记:15.迭代器与生成器

上一篇用类型数组搞定了二进制数据的“高效存储”,这一篇咱们解锁JS遍历的“终极形态”——迭代器(Iterators)与生成器(Generators)。你肯定用过for循环遍历数组,用for...of遍历Set,但有没有想过:为什么数组能直接用for...of,普通对象却不行?为什么有些遍历能“暂停”,比如异步请求依次执行?这些问题的答案,都藏在迭代器和生成器里。

简单说,迭代器是“遍历说明书”——告诉程序如何一步步取出数据;生成器是“智能导游”——不仅能按说明书带路,还能随时暂停、接收指令调整路线。今天咱们就用“旅游”的生活化比喻,把这对“遍历搭档”的原理、用法和实战价值彻底讲透,让你写出更灵活、更优雅的遍历代码。

一、先破案:为什么需要迭代器?普通循环不够用吗?

普通循环(forwhile)就像“自己开车逛景区”——路线得自己规划,停车点得自己记,遇到复杂数据结构(比如树、链表)就手忙脚乱。咱们先看普通循环的三大痛点:

  1. 遍历逻辑不统一:遍历数组要记索引(i从0到length-1),遍历Set用forEach,遍历Map要forEachentries(),每种数据结构一套逻辑,记起来麻烦;
  2. 无法暂停与恢复:循环一旦启动就必须跑完,想在遍历中等待异步任务(比如遍历请求列表,前一个请求完成再发下一个)根本做不到;
  3. 普通对象不能直接遍历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(){letcurrent=1;constmax=5;// 返回迭代器对象(符合迭代器协议)return{next(){if(current<=max){return{value:current++,done:false};}else{return{done:true};// 遍历结束,可省略value}}};}// 使用迭代器constiterator=createNumberIterator();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(){letcurrent=1;return{next(){return{value:current++,done:false};// 永远不结束}};}constinfiniteIt=createInfiniteIterator();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];constarrIt=arr[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](){letindex=0;consthobbies=this.hobbies;return{next(){if(index<hobbies.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对象

四、生成器:简化迭代器的“智能导游”

手动实现迭代器需要维护状态(比如currentindex),麻烦且容易出错。生成器(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;}// 调用生成器函数,返回生成器(迭代器)constgenerator=numberGenerator();// 生成器是迭代器,支持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(start=1,end,step=1){for(leti=start;i<=end;i+=step){yieldi;}}// 遍历1~10,步长2for(constnumofrangeGenerator(1,10,2)){console.log(num);// 1、3、5、7、9}

3. 生成器的暂停与恢复:智能导游的“灵活路线”

yield的核心是“暂停执行”,next()的核心是“恢复执行到下一个yield”。这个特性让生成器能实现“非连续执行”,比如斐波那契数列:

// 斐波那契数列生成器function*fibGenerator(max=Infinity){leta=0,b=1;while(b<=max){yieldb;// 暂停,返回b,下次从这里继续[a,b]=[b,a+b];}}// 遍历前5个斐波那契数constfibIt=fibGenerator();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(start=1){letstep=1;while(true){// 接收next()传入的参数,作为yield的返回值constnewStep=yieldstart;// 如果传了新步长,更新stepif(newStep)step=newStep;start+=step;}}constadjustIt=adjustGenerator(1);console.log(adjustIt.next().value);// 1(第一次传参无效)console.log(adjustIt.next(2).value);// 3(步长改为2:1+2)console.log(adjustIt.next(3).value);// 6(步长改为3:3+3)console.log(adjustIt.next(1).value);// 7(步长改为1:6+1)

2. throw():暂停时抛出异常

throw()方法给生成器抛出异常,异常会在当前暂停的yield处抛出,可在生成器内部捕获:

function*errorGenerator(){try{yield1;yield2;yield3;}catch(err){console.log("捕获异常:",err.message);yield"异常后继续执行";}}consterrIt=errorGenerator();console.log(errIt.next().value);// 1errIt.throw(newError("手动抛出异常"));// 捕获异常:手动抛出异常console.log(errIt.next().value);// 异常后继续执行

3. return():提前终止生成器

return(value)让生成器立即返回{ value, done: true },后续next()都返回{ done: true }

constgen=rangeGenerator(1,5);console.log(gen.next().value);// 1console.log(gen.return("提前终止").value);// 提前终止console.log(gen.next().done);// true

六、实战场景:迭代器与生成器的“用武之地”

1. 场景1:遍历自定义数据结构(树/链表)

迭代器适合遍历复杂数据结构,比如二叉树的中序遍历:

// 二叉树节点classTreeNode{constructor(val){this.val=val;this.left=null;this.right=null;}}// 二叉树中序遍历生成器function*inorderTraversal(root){if(root){yield*inorderTraversal(root.left);// 递归遍历左子树yieldroot.val;// 返回当前节点值yield*inorderTraversal(root.right);// 递归遍历右子树}}// 构建二叉树constroot=newTreeNode(1);root.right=newTreeNode(2);root.right.left=newTreeNode(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){constdata=yieldfetchData(url);// 暂停,等待Promise完成yielddata;// 返回数据}}// 执行异步生成器asyncfunctionrunAsyncGenerator(){consturls=["url1","url2","url3"];constgen=asyncGenerator(urls);letresult=gen.next();while(!result.done){// 如果是Promise,等待其完成constvalue=awaitresult.value;console.log(value);result=gen.next();}}runAsyncGenerator();// 每隔1秒输出一个数据

3. 场景3:无限序列(按需生成,不占内存)

处理大数据量时,生成器按需生成数据,避免一次性加载所有数据导致内存溢出:

// 生成100万以内的偶数(按需生成,不占内存)function*evenGenerator(max=1000000){for(leti=2;i<=max;i+=2){yieldi;}}// 遍历前10个偶数,后面的不生成constevenIt=evenGenerator();for(leti=0;i<10;i++){console.log(evenIt.next().value);// 2、4、6...20}

七、避坑指南:这些坑千万别踩

  1. 迭代器是一次性的:遍历到done: true后,再调用next()也不会重置,需重新创建迭代器;
  2. 生成器不能重复迭代:一个生成器对象只能遍历一次,再次遍历需重新调用生成器函数;
  3. next()第一次传参无效:第一次调用next()时,生成器还没执行到任何yield,传参不会被接收;
  4. 普通对象不是可迭代对象:别直接用for...of遍历普通对象,需手动实现[Symbol.iterator]()
  5. yield只能在生成器函数内使用:普通函数不能用yield,会报错。

八、总结:迭代器与生成器的核心价值

迭代器定义了“统一的遍历协议”,让不同数据结构的遍历逻辑标准化;生成器简化了迭代器的创建,提供了“暂停/恢复”的强大特性,两者结合让JS的遍历能力从“手动开车”升级为“智能导游”。

核心价值总结:

  1. 统一遍历逻辑:for...of通吃所有可迭代对象,不用记多种遍历语法;
  2. 支持复杂场景:无限序列、异步迭代、自定义数据结构遍历,普通循环做不到;
  3. 优化性能:按需生成数据,避免一次性加载大数据导致的内存压力。

掌握它们,你就能从容应对复杂的遍历需求,写出更优雅、更高效的代码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 13:39:49

基于LQR控制算法的四轮独立电驱动汽车横向稳定性控制研究

适用方向&#xff1a;基于LQR控制算法的直接横摆力矩控制&#xff08;DYC&#xff09;的四轮独立电驱动汽车的横向稳定性控制研究 主要内容&#xff1a;利用carsim建模&#xff0c;在simulink中搭建控制器&#xff0c;然后进行联合。 实现汽车在高速低附着路面下完成双移线工况…

作者头像 李华
网站建设 2026/4/23 13:39:56

IPv6违规外联防御指南:从风险盲区到可管可控

在数字化转型加速推进的今天&#xff0c;IPv6作为下一代互联网协议&#xff0c;正以其庞大的地址空间和高效的通信机制&#xff0c;推动着网络基础设施的全面升级。根据APNIC监测数据&#xff0c;2022年全球IPv6支持能力持续增强&#xff0c;支持率从2021年3月的28.31%显著提升…

作者头像 李华
网站建设 2026/4/23 13:14:48

网型逆变器小干扰稳定性分析与控制策略优化:Simulink仿真探索

跟网型逆变器小干扰稳定性分析与控制策略优化simulink仿真模型和代码 现代逆变技术 阻抗重塑 双锁相环 可附赠参考文献&#xff08;英文&#xff09; 和一份与模型完全对应的中文版报告在现代逆变技术的领域中&#xff0c;网型逆变器的小干扰稳定性分析以及控制策略的优化是至关…

作者头像 李华
网站建设 2026/4/23 13:18:24

非支配排序多目标鲸鱼优化算法(NSWOA) Matlab实现探索

非支配排序多目标鲸鱼优化算法(NSWOA) Matlab实现测试函数包括ZDT、DTLZ、WFG、CF和UF共46个等&#xff0c;另外附有一个工程应用案例&#xff1b;评价指标包括超体积度量值HV、反向迭代距离IGD、迭代距离GD和空间评价SP等可提供相关多目标算法定制、创新和改进多目标算法与预测…

作者头像 李华
网站建设 2026/4/23 13:15:41

Solon Plugin 自动装配机制详解

在现代Java生态中&#xff0c;框架的扩展能力是衡量其灵活性和强大程度的重要指标。Solon框架作为一款新兴的Java企业级应用开发框架&#xff0c;其插件扩展机制提供了一种“编码风格”的扩展体系&#xff0c;使开发者能够以更自由、更灵活的方式增强框架功能。一、Solon Plugi…

作者头像 李华
网站建设 2026/4/23 9:48:19

软件开发项目流程图绘制教程,让项目规划一目了然

在软件开发的过程中&#xff0c;你是否常常为项目流程的混乱而烦恼&#xff1f;各个环节之间的衔接不清晰&#xff0c;导致团队成员沟通成本增加&#xff0c;项目进度也受到影响。其实&#xff0c;解决这个问题的关键在于绘制一份清晰、准确的流程图。今天&#xff0c;我们就来…

作者头像 李华