当前位置: 首页 > Web前端 > HTML

两千字助你理解for of原理,扩展for of完美解决遍历对象问题

时间:2023-03-28 17:50:24 HTML

两千字帮你理解for的原理,扩展for完美解决对象遍历问题。forof可以轻松遍历数组、集合、map,写法也很简洁。在我的项目中,除了需要获取具体的返回值外,使用了数组的map-filter-reduce方法,其余的遍历都是forof完成的。在这篇文章中,我将逐层介绍forof语句的用法和注意事项,并分析其原理——迭代器和生成器,最后在对象和数字类型上扩展forof的功能。语法及优点forof语句的语法如下:for(letvariableofiterable){//statements}iterable是要遍历的目标,通常是数组、字符串、集合、map或者其他实现了iterator接口Array对象,如函数参数列表arguments、DOM节点列表NodeList变量是自己定义的变量,用于存放迭代器每次迭代的返回值。您可以查看下面的示例以更好地了解如何使用它。迭代数组,变量存储数组的值。让可迭代=[10,20,30];for(letvalueofiterable){console.log(value);}//10//20//30迭代字符串,变量存储单个字符。letiterable="boo";for(letvalueofiterable){console.log(value);}//"b"//"o"//"o"迭代集合,变量存储值的集合。让iterable=newSet([1,1,2,2,3,3]);for(letvalueofiterable){console.log(value);}//1//2//3iterationmap,variable存放的是键值对数组,一般通过解构赋值来使用。letiterable=newMap([["a",1],["b",2],["c",3]]);for(letentryofiterable){console.log(entry);}//["a",1]//["b",2]//["c",3]for(let[key,value]ofiterable){console.log(key,value);}//a1//b2//c3与传统的循环语句相比,forof语句更加简洁。传统的循环无法遍历集合和映射,因为它们没有索引。breakcontinue操作符可以用在forof中来结束或终止迭代,这使得它超越了forEach方法。注意事项forof虽然使用方便,但要注意以下问题:不能直接遍历对象。该对象没有实现iterator接口,直接遍历会抛出异常。如果要遍历一个对象的属性,可以先通过Object.keys()方法获取该对象的属性列表,然后再进行遍历。无法实现数组赋值。forof在遍历数组时不提供索引,不能直接修改数组。如果打算对数组进行变异,建议使用其他遍历方法。不要提前修改联合项目。如果在遍历过程中修改了后面项的值,则在后续迭代中会得到新的值。不要在遍历过程中添加或删除项目。如果在遍历过程中删除未迭代的项,会导致迭代次数减少;如果您在遍历过程中添加新项目,它们也会被迭代。前两个问题将在forof部分的扩展中得到完美解决。另外两个问题就是我们在遍历时应该遵循的规范。如果我们不遵循它们,代码逻辑就会混乱。预备知识要想理解forof语句的原理,首先要理解三个概念:迭代协议、迭代器、生成器。迭代协议MDN对迭代协议的介绍比较复杂。我将简要总结一下。详情请点击链接。一个对象为了可迭代,需要实现一个迭代器接口,它的值应该是一个指定的迭代器。在JS中,对象的迭代接口通过属性Symbol.iterator暴露给开发者。迭代器迭代器是具有next方法的对象,该方法返回具有两个属性value和done的对象。value表示本次迭代的值;done表示是否已经迭代到序列中的最后一个。迭代器对象可以重复调用next()方法。这个过程称为迭代一个迭代器,也称为消耗迭代器,因为它通常只能迭代一次。在产生最终值之后,对next()的额外调用将??简单地返回{value:finalvalue,done:true}。迭代器的写法比较复杂,这里只举一个例子。此函数用于生成迭代器对象。无需过多研究,因为我们不需要手写迭代器,JS为我们提供了方便的生成器来生成迭代器对象。//数字从start开始逐步递增,直到大于endfunctionmakeRangeIterator(start=0,end=Infinity,step=1){letnextIndex=startconstrangeIterator={next:function(){letresultif(nextIndex<=end){zresult={value:nextIndex,done:false}nextIndex+=step}else{result={value:undefined,done:true}}returnresult},}returnrangeIterator}生成器是一个函数,我将在语法和调用方面详细说明生成器函数。语法:生成器函数的语法有一定的规则。该函数应使用function*语法编写。其中,yield关键字可以用来指定每次迭代产生的值,return关键字也可以作为迭代器的最终值。调用:调用生成器函数只会返回一个迭代器对象,不会执行函数体中的代码。通过调用generator的next()方法,执行函数体的内容,直到遇到yield关键字或执行完毕。光看文字很难理解,我们来看一个例子('第三次调用')return'c'}letiterator=generator()console.log('createiterator')console.log('next1:',iterator.next())console.log('next2:',iterator.next())console.log('next3:',iterator.next())console.log('next4:',iterator.next())控制台打印如下:生成函数的内容It逐步调用,每次迭代只运行到下一次yield的位置,输出yield关键字后的表达式作为本次迭代的值。当遇到返回或者函数执行时,返回对象的done属性会被设置为true,表示迭代器被完全消耗。把前面的例子改成使用生成器,代码很简洁://从start开始数,每次迭代递增step,直到大于endfunction*makeRangeIterator(start=0,end=Infinity,step=1){for(leti=start;i<=end;i+=step){yieldi}}next方法可以传参,参数会以yield关键字的返回值形式使用,参数在第一个next调用中传递的将被忽略。看看下面这个无限累加器,传递0将重置它:function*generator(start){letcur=startwhile(true){letnum=yieldcurif(num==0){console.log('iterator被重置')cur=start}else{cur+=num}}}letiterator=generator(10)console.log('createiterator')console.log('next1:',iterator.next().value)console.log('next2:',iterator.next(2).value)console.log('next3:',iterator.next(4).value)console.log('next4:',iterator.next(5).value)console.log('next5:',iterator.next(0).value)console.log('next6:',iterator.next(5).value)console.log('next7:',iterator.next(10).value)控制台输出如下原理,实现forof的原理,就是调用target的iterator接口(generator函数)获取迭代器,然后不断迭代迭代器返回object变量的value属性被赋值给变量,直到返回对象的done属性为真。函数简单实现:/***@description:forof方法实现*@param{object}iteratorObj可迭代对象*@param{Function}fn回调函数*@return{void}*/functionmyForOf(iteratorObj,fn){//如果传入的对象没有迭代器接口,抛出异常if(typeofiteratorObj[Symbol.iterator]!='function'){thrownewTypeError(`${iteratorObj}isnotiterable`)}//获取迭代器letiterator=iteratorObj[Symbol.iterator]()//遍历迭代器letiwhile(!(i=iterator.next()).done){fn(i.value)}}constarr=[10,20,30]myForOf(arr,(item)=>{console.log(item)})letmap=newMap([['a',1],['b',2],['c',3],])myForOf(map,([key,value])=>{console.log(key,value)})控制台输入如下:forof与forEach方法的原理一致,以上代码为稍微修改forEach方法可以扩展。forof我们知道for的美中不足的是不能直接遍历对象的属性。我们只需要在Object原型对象上实现迭代接口,将其定义为返回对象所有属性的生成器即可。实现如下:Object.prototype[Symbol.iterator]=function*(){constkeys=Object.keys(this)for(leti=0;i