这两个ES2015新增的特性以前不是很清楚,或者看了教程后很容易忘记。这次,我将写一篇博文,以增强理解和记忆。Iterator迭代器中文称为迭代器,顾名思义就是用来循环遍历的。平时我们说到循环遍历,肯定会想到for、while循环、数组的内置方法。关于for和while循环就不多说了。这是语言中最基本的循环迭代方法。缺点是非常简单的迭代需要写一些模板代码,不好用。所以对于那些常用的需要遍历的数据类型,比如Array,JS提供了forEach、map、filter等方便的方法,具有特定的功能。当然,这些方法属于数组,没办法使用其他我们要遍历的数据结构,比如字符串,所以ES2015增加了for...of循环,可以用来遍历任何可迭代对象。那么问题来了,这个可迭代对象是什么?这就涉及到了ES2015新增的概念,可迭代协议(iterableprotocol)和迭代器协议(iteratorprotocol)。可迭代协议可迭代协议规定一个可迭代对象必须实现一个名为Symbol.iterator的方法,该方法必须返回一个迭代器对象。上面的问题已经有了答案,只要一个对象本身或者原型链中有这个方法,那么这个对象就是可迭代的。//这样的对象是可迭代对象constiterableObj={//必须有这个方法[Symbol.iterator](){return;//这个方法必须返回一个迭代器对象}};之后怎么样了?这就是迭代器协议。迭代器协议迭代器协议规定迭代器对象必须有一个next方法,该方法必须返回一个具有done和value属性的对象。这个比较抽象,下面的代码示例可以形象化迭代器对象://这样一个对象就是迭代器对象constitratorObj={next(){return{done:false,//当值为真时,迭代结束值:null,//done为true时每次迭代返回的值可以省略};}};弄清了以上两个基本概念后,我们就明白什么是可迭代对象了。有些内置对象自己实现了iterable协议,所以是可迭代的(可以用for...of遍历),比如Array、String、Set、Map等。当然我们定义的对象也可以是可迭代的,只要我们自己实现可迭代协议。这是一个例子。const范围={从:1,到:5,};我们有上面的对象,如果直接使用for...of循环遍历,肯定会报错,报错信息是rangeisnotiterable。我们想让它成为可迭代的,按照我们想要的方式进行迭代:从迭代from到to,我们只需要实现iterable协议即可。constrange={form:1,to:5,[Symbol.iterator](){//怎么做?return{next(){//下面两个属性什么时候改变?返回{完成:假,值:1,};}};}};说起来容易,但是如何实现这段代码呢?这就涉及到Symbol.iterator方法的运行机制。对象迭代时,会在迭代前执行Symbol.iterator方法,然后使用该方法返回的iterator对象进行迭代控制。通俗地说就是每次迭代执行一次next方法,将value值作为迭代的返回值。当done为真时,迭代结束。按照这个思路,我们就可以实现我们自定义的迭代方法了。constrange={from:1,to:5,[Symbol.iterator](){让cur=this.from;//在迭代前初始化当前迭代值constto=this.to;//临时存储终止值return{next(){constdone=cur>to;//当迭代值大于to时,迭代结束return{done,value:done?undefined:cur++,//返回迭代值};}};}};//验证for(letitemofrange){//这里注意:for...of迭代操作会忽略done的值为trueconsole.log(item);//12345}一般来说,建议对象同时实现可迭代协议和迭代器协议,称为良构可迭代对象,像这样:constrange={from:1,to:5,//对象有一个next方法,符合迭代器协议next(){constdone=this._iteratorVal>this.to;返回{完成,值:完成?未定义:this._iteratorVal++,};},[Symbol.iterator](){this._iteratorVal=this.from;归还这个;//可迭代协议返回对象本身}};我们可以实现一个生成可迭代对象的通用方法forEach,这样更方便迭代非数组的可迭代对象。functioniterableForEachFactory(obj){returnfunction(callback){letindex=0;for(letitemofobj){callback(item,index);索引++;}}}constrangeForEach=iterableForEachFactory(range);rangeForEach((item,index)=>console.log(item,index));//12345,01234到目前为止我们只在对象中使用了迭代器,有时我们只需要一个迭代器来执行自定义的迭代逻辑,类似于for和while循环,但是forwhile循环是自动的.一旦迭代开始,就不能暂停,只能彻底跳出循环。使用迭代器协议,我们可以实现一个可以暂停的手动循环,并且可以自定义循环逻辑。函数iteratorMaker(param){让索引=0;让lastValue=null;//param可以是代表迭代次数的数字,也可以是自定义迭代逻辑的函数,也可以不传constisFunction=typeofparam==='function';constisNumber=typeofparam==='number';return{next(){constnextIndex=index++;lastValue=isFunction?param(lastValue,nextIndex):nextIndex;//如果传入自定义迭代函数,则返回undefined可以终止循环constdone=isFunction?lastValue===未定义:isNumber?nextIndex>=param:false;返回{完成,值:完成?未定义:lastValue,};}};}//循环两次普通迭代器constiteratorCircle2=iteratorMaker(2);iteratorCircle2.next().value;//0iteratorCircle2.next().value;//1iteratorCircle2.next().value;//undefined//每次iteratorconstiteratorDoubleCircle=iteratorMaker(doubleCircle);functiondoubleCircle(lastValue){returnlastValue?lastValue*2:1;}iteratorDoubleCircle.next().value;//1iteratorDoubleCircle.next().值;//2iteratorDoubleCircle.next().value;//4iteratorDoubleCircle.next().value;//8生成器。这很简单。可以这么简单粗暴的理解:Generator是Iterator的语法糖,但是它有更高级的功能。他们的关系很容易想到Proxy和Reflect。Reflect可以完美匹配Proxy的语法,而且它还有自己的附加功能。语法function*gen(){yield1;产量2;产量3;返回;//return是可选的,等同于done:true}//这是生成器对象,它同时实现了可迭代协议和迭代器协议constg=gen();typeofg[Symbol.iterator];//g.next的函数类型;//functiong[Symbol.iterator]()===g;//true这看起来眼熟吗?,是的,生成器对象与我们之前实现的格式良好的可迭代对象范围非常相似。我们可以使用生成器轻松重写范围示例:constrange={from:1,to:5,*[Symbol.iterator](){for(leti=this.from;i<=this.to;i++){产量我;}}};//验证(letitemofrange){console.log(item);//12345}combinationGenerator可以嵌套组合,实现各种不同的功能。function*generateSequence(start,end){for(leti=start;i<=end;i++)yieldi;}function*generatePasswordCodes(){//0..9yield*generateSequence(48,57);}//组合语法是yield*后跟任何生成器函数//A..Zyield*generateSequence(65,90);//a..zyield*generateSequence(97,122);}letstr='';for(letcodeofgeneratePasswordCodes()){str+=String.fromCharCode(code);}console.log(str);//0..9A..Za..z也可以使用组合递归实现数组扁平化constarr=[1,2,3,[4,5,[6,7,8,[9]]]];function*flatten(arr){for(letitemofarr){if(Array.isArray(item)){yield*flatten(item);}}else{收益项目;}}}constflatArr=[...flatten(arr)];控制台日志(flatArr);//[1,2,3,4,5,6,7,8,9]仔细阅读two-wayyield的读者会发现,到目前为止,我们还不能使用生成器来实现上面的iteratorMaker函数。这里需要介绍一下生成器最强大的概念之一:two-wayyield。yield给人的感觉和iterator中的value一样,迭代的向外部输出值,但是它也可以接收外部next函数传入的值,作为下一次执行的输出结果。function*gen(){const结果=yield1;//将yield表达式赋值给一个变量,实现双向yieldyieldresult*2;//next函数传入的下一个值会赋值给result}constg=gen();g.next().value;//1g.next(2).value;//4理解了这个特性后,我们可以用generator重写iteratorMaker函数:functioniteratorMaker(param){letindex=0;让lastValue=null;constisFunction=typeofparam==='函数';constisNumber=typeofparam==='number';function*gen(){让passValue=null;while(isNumber?index<=param:lastValue!==undefined){passValue=yield(passValue===null?lastValue:initial);}}constg=gen();return{next(){constnextIndex=index++;lastValue=isFunction?参数(lastValue,nextIndex):nextIndex;返回g.next(lastValue);}};}总的来说,Iterator和Generator是ES2015中比较少用到的特性,它们也有一定的复杂性,尤其是Generator的双向yield,需要多写例子才能理解。我希望这篇文章对你有用。