当前位置: 首页 > 后端技术 > Java

面试官:当你有一个for循环时,为什么你需要forEach??

时间:2023-04-01 18:05:25 Java

作者:科技直男明星\来源:juejin.cn/post/7018097650687803422jsjs中有很多循环,for...infor...offorEach,有些循环感觉很像,今天我们将讨论forloop和forEach的区别。我们从几个维度来讨论:for循环和forEach的本质区别。for循环和forEach的语法区别。for循环和forEach的性能差异。本质上的区别在于,for循环是js提出时就存在的一种循环方式。forEach是ES5提出的一种挂载在可迭代对象原型上的方法,比如ArraySetMap。forEach是一个迭代器,负责遍历可迭代对象。那么什么是遍历、迭代、可迭代对象呢?遍历:指对数据结构的每个成员进行定时、一次性访问的行为。迭代:迭代是递归的一种特殊形式。它是迭代器提供的一种方法。默认情况下,它按照一定的顺序一个一个地访问数据结构成员。迭代也是一种遍历行为。可迭代对象:在ES6中引入了可迭代类型,ArraySetMapStringargumentsNodeList都属于可迭代对象,它们的特点是都有[Symbol.iterator]方法,包含它的对象被认为是可迭代的可迭代对象。了解了这些就知道forEach其实就是一个迭代器。它和for循环的本质区别是forEach负责遍历(ArraySetMap)可迭代对象,而for循环是一种循环机制,只能通过它。出数组。让我们谈谈什么是迭代器。还记得前面提到的Generator生成器吗。当它被调用时,会生成一个迭代器对象(IteratorObject)。它有一个.next()方法,每次调用返回一个Object{value:value,done:Boolean},value返回yield后的返回值,当yield结束时,done变为true,通过不断调用获取内部值和顺序迭代。迭代器是一种特殊的对象。在ES6规范中,它的符号是返回对象的next()方法,在done中判断迭代行为。迭代器在不暴露内部表示的情况下实现遍历。看代码letarr=[1,2,3,4]//可迭代对象letiterator=arr[Symbol.iterator]()//调用Symbol.iterator后生成迭代器对象console.log(iterator.next());//{value:1,done:false}访问迭代器对象的next方法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:undefined,done:true}我们看到了。只要是可迭代对象,调用内部的Symbol.iterator就会提供一个迭代器,根据迭代器返回的next方法访问内部,这也是for...of的实现原理。letarr=[1,2,3,4]for(constitemofarr){console.log(item);//1234}调用next方法返回对象的值,保存在item中,直到Ifvalueundefined,跳出循环,所有可迭代对象都可以被for...of消费。让我们看看其他可迭代对象:functionnum(params){console.log(arguments);//Arguments(6)[1,2,3,4,callee:?,Symbol(Symbol.iterator):?]letiterator=arguments[Symbol.iterator]()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}控制台。日志(迭代器。下一个());//{value:undefined,done:true}}num(1,2,3,4)letset=newSet('1234')set.forEach(item=>{console.log(item);//1234})letiterator=set[Symbol.iterator]()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:undefined,done:true}所以我们也可以直观的看出可迭代对象中的Symbol.iterator属性在调用时可以生成迭代器,forEach还生成一个迭代器,在内部回调函数中传递每个元素的值(有兴趣的同学可以搜索Each源码,forEach是挂载在ArraySetMap实例上的,不过网上大部分的回答都是通过length来使用的)判断长度,使用for循环机制实现,但是用在SetMap上会报错,所以我认为是调用的迭代器,不断调用next,将参数传给回调函数.由于网上没有找到答案,我就不下定论了,有答案的可以在评论区留言)for循环和forEach的语法区别理解本质区别。在应用过程中,它们在语法上有什么区别?forEach的参数。forEach的中断。forEach删除自己的元素,索引无法重置。for循环可以控制循环的起点。forEach的参数我们真的知道forEach完整的参数内容吗?大致是这样的:arr.forEach((self,index,arr)=>{},this)self:数组当前遍历的元素,默认从左到右依次获取数组元素。index:数组当前元素的索引,第一个元素的索引为0,以此类推。arr:当前遍历的数组。this:回调函数中的this点。letarr=[1,2,3,4];letperson={name:'技术直男明星'};arr.forEach(function(self,index,arr){console.log(`当前元素为${self}的索引为${index},属于数组${arr}`);console.log(this.name+='真帅');},person)我们可以使用arr来实现数组去重:让arr1=[1,2,1,3,1];让arr2=[];arr1.forEach(function(self,index,arr){arr.indexOf(self)===index?arr2.push(self):null;});console.log(arr2);//[1,2,3]forEach中断在js中有breakreturncontinue中断函数或者跳出循环。我们会在for循环中使用一些中断行为,对于优化数组遍历查找很有好处,但是因为forEach属于iterator,只能顺序遍历,所以不支持上面的中断行为。让arr=[1,2,3,4],i=0,length=arr.length;for(;i{console.log(self);if(self===2){break;//报错};});arr.forEach((self,index)=>{console.log(self);if(self===2){continue;//报错};});如果一定要跳出forEach循环呢?其实有办法,用try/catch:try{vararr=[1,2,3,4];arr.forEach(function(item,index){//退出条件if(item===3){thrownewError("LoopTerminates");}//dosomethingconsole.log(item);});}catch(e){if(e.message!=="LoopTerminates")抛出e;};如果遇到return不会报错,但不会生效letarr=[1,2,3,4];functionfind(array,num){array.forEach((self,index)=>{if(self===num){returnindex;};});};letindex=find(arr,2);//undefinedforEach删除自己的元素,索引不能重置我们无法控制forEach中index的值,它只会无意识地增加,直到大于数组长度跳出循环,所以它无法删除自己来重置index。让我们看一个简单的例子:让arr=[1,2,3,4]arr.forEach((item,index)=>{console.log(item);//1234index++;});index在函数体中不会随增减而改变。在实际开发中,遍历一个数组,同时删除一个元素是很常见的。使用forEach删除时要注意。for循环可以控制循环的起点。上面说了,forEach的循环起点只能是0,没有人为干预,但是for循环不一样:设arr=[1,2,3,4],i=1,length=arr。length;for(;iindex!==1);console.log(arr1)//[2]for循环和forEach的性能差异在性能比较方面,我们添加了一个map迭代器,它像filter一样生成一个新的数组。我们比较forforEachmap在浏览器环境下的性能:性能比较:for>forEach>map在chrome62和Node.jsv9.1.0环境下:for循环比forEach快一倍,forEach比forEach快map大约快20%。原因分析:for循环没有额外的函数调用栈和上下文,所以它的实现最简单。forEach:对于forEach,其函数签名包含参数和上下文,因此性能会低于for循环。map:map之所以最慢,是因为map会返回一个新的数组,数组的创建和赋值会导致内存空间的分配,会带来很大的性能开销。如果map嵌套在循环中,会带来更多不必要的内存消耗。当你使用迭代器遍历一个数组时,如果你不需要返回一个新的数组,那么使用map是违背设计初衷的。在我的前端合作开发中,看到很多人使用map只是为了遍历数组:letdata=[];让data2=[1,2,3];data2.map(item=>data.push(item));写在最后:这是面试中遇到的问题。那时,我只知道语法上的区别。并没有进一步从多个角度区分可迭代对象、迭代器、生成器和性能之间的异同。也希望把一个简单的问题从多个角度详细解释一下,让大家看得透彻。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.20w程序员红包封面,快拿。..5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!