本周的精读文章是IterablesandIterationprotocols。根据为什么需要迭代器,迭代器是如何设计的,以及我们如何使用迭代器进行扩展。概述为什么需要迭代器,因为用for...of来循环数组很方便,但是如果只有数组支持这种语法就太麻烦了。比如我们自然希望for...of能够遍历一个字符串的每一个字符,希望newSet([1,2,3])能够快速初始化一个新的Set。上面提到的这些能力,JS都支持,那么为什么JS引擎会知道如何遍历字符串呢?如何知道数组[1,2,3]与Set类型的各个Key的对应关系呢?实现这些功能背后的原理是迭代器(Iterables)。因为Array和Set是可迭代的,所以可以通过for...of遍历它们,JS引擎自然知道它们之间的关系。迭代器是如何设计的迭代器有两种定义方式,一种是独立定义,一种是组合在对象中。它可以独立定义为扩展[Symbol.iterator]属性的对象。规范之所以采用[Symbol.iterator]是为了防止普通字面量Key与对象本身的OwnProperties冲突:constobj={}obj[Symbol.iterator]=function(){return{someValue:1,next(){//通过this.someValue可以访问和修改value,迭代过程中可以定义任意数量的变量作为辅助变量if(...){return{done:false,value:this.current++}//表示迭代还是Endless,当前值为value}return{done:true}//表示迭代完成}};};在for...of中,只要done:true没有被读到,就会一直循环下去。对象中简化了合并的定义。可以在对象中定义迭代:letrange={from:1,to:5,[Symbol.iterator](){this.current=this.from;归还这个;},next(){if(this.current<=this.to){return{done:false,value:this.current++};}else{return{done:true};}},};这种定义的缺点是并行迭代对象可能会触发BUG,因为每次迭代共享相同的状态变量。手动控制迭代迭代器也可以自定义触发器,如下:constmyObj=iterable[Symbol.iterator]();myObj.next();//{值:1,完成:假}myObj.next();//{value:2,done:false}myObj.next();//{值:3,完成:假}myObj.next();//{done:true}你知道当done为真时迭代停止。手动控制迭代的好处是可以自由控制next()触发的时机和频率,甚至可以提前终止,带来更多的自由度。iterable和ArrayLike的区别如果不了解迭代器,你可能会认为forof是通过下标访问的,你会混淆一个对象是否可以用obj[index]访问,是否可迭代。看完上面的介绍,你应该明白了,可迭代性的产生是为了实现[Symbol.iterator],与对象是数组还是ArrayLike无关。//这个对象是可迭代的,不是ArrayLikeconstrange={from:1,to:5,};range[Symbol.iterator]=function(){//...};//这个对象是不可迭代的,它是ArrayLikeconstrange={"0":"a","1":"b",length:2,};//对象是可迭代的,是一个ArrayLikeconstrange={"0":"a","1":"b",length:2,};范围[Symbol.iterator]=function(){//...};对了,js的数组类型就是典型的iterable和ArrayLike类型。精读和可迭代的内置类型String、Array、TypedArray、Map、Set都支持迭代,其表现是:constmyString="abc";for(letvalofmyString){console.log(val);}//'a','b','c'constmyArr=["a","b","c"];for(letvalofmyArr){console.log(val);}//'a','b','c'constmyMap=[["1","a"],["2","b"],["3","c"],];for(letvalofmyMap){console.log(val);}//['1','a'],['2','b'],['3','c']constmySet=newSet(["a","b","c"]);for(letvalofmySet){console.log(val);}//'a','b','c'可迭代对象可以应用于哪些API可迭代对象首先支持上述语法中的for...of和for...。另外,很多内置函数的入参都支持传递可迭代对象:Map()WeakMap()Set()WeakSet()Promise.all()Promise.allSettled()Promise.race()Promise.any()Array。从()。例如,Array.from语法可以将一个可迭代对象变成一个真正的数组。数组下标为执行next()的次数,值为next()。value:Array.from(newSet(["1","2","3"]));//['1','2','3']generator也是迭代器的一种,属于异步迭代器,所以你甚至可以像上面那样yield一个生成器函数这些内置函数的参数:newSet((函数*(){产量1;产量2;产量3;})());最后一篇是上周精读中提到的精读《Rest vs Spread 语法》,解构的本质也是利用迭代器操作:constrange={from:1,to:5,[Symbol.iterator](){this.current=this.from;归还这个;},next(){if(this.current<=this.to){return{done:false,value:this.current++};}else{return{done:true};}},};[...范围];//[1,2,3,4,5]综上所述,在生活中,我们可以数苹果有多少,建筑物的窗户有多少,衣服乱七八糟的有多少。事实上,这些对象的排列在不同的场景中是不同的。连老师都在黑板上写了0~10。我们按照这4个字符也可以从1数到10,将这背后的原理作为迭代器抽象到程序中。一个对象黑盒,不管它内部是如何实现的,如果我们能按顺序统计内部结构,那么这个对象就是可迭代的,这就是[Symbol.iterator]定义要解决的问题。生活和程序中都有一些默认的迭代器,大家可以仔细了解一下它们之间的关系。讨论地址为:精读《迭代器 Iterable》·Issue#448·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号
