助力软件开发企业降本增效 PHP / java源码系统,只需一次付费,代码终身使用! 广告
[toc] ### 迭代器 #### 背景知识 1. 什么是迭代? 从一个数据集合中按照一定的顺序,不断取出数据的过程 2. 迭代和遍历的区别? 迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完 遍历强调的是要把整个数据依次全部取出 3. 迭代器 迭代器是对迭代过程的一个封装,在不同的语言中有不同的表现形式,通常为对象 4. 迭代模式 一种设计模式,用于统一迭代过程,并规范了迭代器规格: - 迭代器应该具有得到下一个数据的能力 - 迭代器应该具有判断是否还有后续数据的能力 #### JS中的迭代器 JS规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下: ```js {value: 值, done: 是否迭代完成} ``` 则认为该对象是一个迭代器 含义: - next方法:用于得到下一个数据 - 返回的对象 - value:下一个数据的值 - done:boolean,是否迭代完成 例如:我们可以通过迭代器来获取斐波那契数列中某一位的值。 ```js function createFeiboIterator() { //定义一个斐波那契数列生成器 let prev1 = 1, //当前位置的前一位的值 prev2 = 1, //当前位置的前两位的值 n = 1; //当前是第几位 return { next() { let value; if (n <= 2) { value = 1; } else { value = prev1 + prev2; } const result = { value, done: false, }; prev2 = prev1; prev1 = result.value; n++; return result; }, }; } const fbNum = createFieboIterator(); //生成一个斐波那契数列的可迭代对象 fbNum.next(); //输出:{value:1, done:false} fbNum.next(); //输出:{value:1, done:false} fbNum.next(); //输出:{value:2, done:false} fbNum.next(); //输出:{value:3, done:false} fbNum.next(); //输出:{value:5, done:false} fbNum.next(); //输出:{value:8, done:false} ``` ### 可迭代协议 与 for-of 循环 #### 可迭代协议 **概念回顾** - 迭代器(iterator):一个具有next方法的对象,next方法返回下一个数据并且能指示是否迭代完成 - 迭代器创建函数(iterator creator):一个返回迭代器的函数 **可迭代协议** ES6规定,如果一个对象具有知名符号属性```Symbol.iterator```,并且属性值是一个迭代器创建函数,则该对象是可迭代的(iterable) 例如: ```js var obj = { a: 1, b: 2, [Symbol.iterator]() { const keys = Object.keys(this); let i = 0; return { next: () => { const propName = keys[i]; const propValue = this[propName]; const result = { value: { propName, propValue }, done: i >= keys.length } i++; return result; } } } } //按照上面的方法创建一个obj对象,我们为obj对象手动添加了[Symbol.iterator]属性,那么该对象就是一个可迭代对象 ``` > 思考:如何知晓一个对象是否是可迭代的? 看一下该对象是否具有"Symbol.iterator"属性,并且其属性值是一个迭代器创建函数。 例如:在ES6中,数组本身就是一个可迭代对象。其prototype上本身具有Symbol.iterator属性。 ```js const arr = [1,2,3,4,5,6]; //创建一个数组 const arrIterator = arr[Symbol.iterator](); //使用数组原型中的Symbol.iterator属性创建一个该数组的迭代器 console.log(arrIterator.next()); //第一次执行会返回{value:1, done:false},再次执行会依次向后迭代。 ``` 在web Api中DOM对象等伪数组也是可迭代对象。 > 思考:如何遍历一个可迭代对象? 最基本的方法,可以使用while循环来遍历一个可迭代对象中的每一项的值。 ```js const iterator = arr[Symbol.iterator](); let result = iterator.next(); while (!result.done) { const item = result.value; //取出数据 console.log(item); //下一次迭代 result = iterator.next(); } ``` #### for-of 循环 基于上面的遍历模式,ES6为我们提供了一个新的方法:for-of 循环用于遍历可迭代对象,格式如下 ```js //迭代完成后循环结束 for(const item of iterable){ //iterable:可迭代对象 //item:每次迭代得到的数据 } ``` 基于for-of循环,遍历上面的数组可以写成: ```js for (const item of arr){ console.log(item) } //执行结果:依次输出1,2,3,4,5,6 ``` #### 展开运算符与可迭代对象 **展开运算符可以作用于可迭代对象,这样,就可以轻松的将可迭代对象转换为数组。** 对可迭代对象使用展开运算符,相当于对该对象使用了for-of,并将其返回的每个值追加到数组中。 ### 生成器 (Generator) 1. 什么是生成器? 生成器是一个通过构造函数Generator创建的对象,生成器既是一个迭代器,同时又是一个可迭代对象 2. 如何创建生成器? 生成器的创建,必须使用生成器函数(Generator Function) 3. 如何书写一个生成器函数呢? 只需要在function后面或定义的函数名前面加上*号即可。 ```js //这是一个生成器函数,该函数一定返回一个生成器 function* test(){ } const iterator = test(); //调用该函数,并没有执行函数体中的代码,而是创建了一个可迭代对象iterator for(const item of iterator){}; //执行这条语句并没有报错,说明iterator是一个可迭代对象。 ``` 4. 生成器函数内部是如何执行的? 生成器函数内部书写的代码,是为了给生成器的每次迭代提供数据的。 生成器生成可迭代对象后,只有该可迭代对象调用next方法时才会执行生成器函数体内部的代码。 每次调用生成器的next方法,将导致生成器函数运行到下一个yield关键字位置 yield是一个关键字,该关键字只能在生成器函数内部使用,表示“产生”一个迭代数据。 代码示例: ```js function* test(){ console.log('第1次运行next'); yield first; //位置A console.log('第2次运行next'); yield second; //位置B console.log('第3次运行next'); //位置C } const ite = test(); //调用test()生成器,创建一个可迭代对象ite,此时test函数体中的代码不会执行任何语句 console.log(ite.next()); //当第一执行ite的next方法时,开始执行test中的代码,从第一行开始执行到第一个yield的位置,即执行到位置A即停止执行。并将yield后面的值做为value的值返回。此时输出的结果为:第1次运行next {value: 'first', done: false} console.log(ite.next()); //当再次执行ite的next方法时,继续执行test中的代码,从位置A开始执行到第二个yield的位置,即执行到位置B即停止执行。并将yield后面的值做为value的值返回。此时输出的结果为:第2次运行next {value: 'second', done: false} console.log(ite.next()); //当第三次执行ite的next方法时,继续执行test中的代码,从位置B开始执行到代码结束,停止执行。因为代码已结束,没有yield关键字返回值。此时输出的结果为:第3次运行next {value: undefined, done: true}。表示迭代已完成 ``` >由此总结,执行由生成器生成的可迭代对象的next方法时,会从第一句开始执行生成器函数体中的语句,当遇到yield关键字时停止向下执行语句,并将yield的值做为value值返回。再次执行next方法时,会从上一次yield位置的下一条语句开始执行,直到遇到下一个yield关键字停止。 根据生成器的规则,我们可以简化上节中斐波那契生成器的代码 ```js //创建一个斐波那契数列的生成器 function* createFeiboIterator() { let prev1 = 1, prev2 = 1, //当前位置的前1位和前2位 n = 1; //当前是第几位 while (true) { if (n <= 2) { yield 1; } else { const newValue = prev1 + prev2 yield newValue; prev2 = prev1; prev1 = newValue; } n++; } } const fbNum = createFieboIterator(); //生成一个斐波那契数列的可迭代对象 fbNum.next(); //输出:{value:1, done:false} fbNum.next(); //输出:{value:1, done:false} fbNum.next(); //输出:{value:2, done:false} fbNum.next(); //输出:{value:3, done:false} fbNum.next(); //输出:{value:5, done:false} fbNum.next(); //输出:{value:8, done:false} ``` >生成器的目的就是使迭代器的代码更加简洁。 5. 有哪些需要注意的细节? 1). 生成器函数可以有返回值,返回值出现在第一次done为true时的value属性中 2). 调用生成器的next方法时,可以传递参数,传递的参数会交给yield表达式的返回值 3). 第一次调用next方法时,传参没有任何意义 4). 在生成器函数内部,可以调用其他生成器函数,但是要注意在yield后面加上*号 例如: ```js function* test1(){ yield a; yield b; } function* test(){ yield* test1(); yield 1 yield 2 } const t = test(); t.next(); //{value:a, done:false} t.next(); //{value:b, done:false} t.next(); //{value:1, done:false} ``` 6. 生成器的其他API - return方法:调用该方法,可以提前结束生成器函数,从而提前让整个迭代过程结束 - throw方法:调用该方法,可以在生成器中产生一个错误