一、迭代器
迭代就是遍历一个东西,不一定要遍历完。
为什么需要迭代器?因为使用循环来迭代不理想,具体有两方面原因:
- 迭代之前需要事先知道如何使用数据结构。如利用数组下标[]来取得特定索引位置上的项
- 遍历顺序并不是数据结构固有的。如通过递增索引来访问数据,只有数组能这样
我们需要的是一种通用迭代方式,能对自己定义的任何对象进行迭代的方法。这个方法就是使用迭代器
这里需要分清三个概念:可迭代对象、迭代器工厂函数、迭代器:
let str = ['a','b','c']; // 可迭代对象 console.log(str[Symbol.iterator]) // 打印出迭代器工厂函数 console.log(str[Symbol.iterator()]) // 调用迭代器工厂函数会生成一个迭代器
实现迭代器接口意味着该可迭代对象有个 Symbol.iterator 键(作为默认迭代器属性),它引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。JS中实现迭代器接口的对象有 String Array Map Set DOM, 这些对象在如 for - of 等语法下会自动调用迭代器工厂函数创建迭代器,然后调用迭代器的 next 方法,返回一个对象包含 done 和 value:
class Counter{ constructor(limit){ this.limit=limit; } // 这就叫实现了迭代器接口,它返回一个迭代器对象,next是其方法 [Symbol.iterator](){ let count=1, limit=this.limit; return { next(){ if(count<=limit){ return {done:false,value:count++}; // 若迭代还没完 }else{ return {done:true,value:undefined}; // 若迭代结束 } } } } } let counter = new Counter(3); for(let i of counter){console.log(i)} // 1 2 3 for(let i of counter){console.log(i)} // 1 2 3
return() 方法可以用来提前关闭迭代器,当 for-of 结构遇到 break、continue、return 、throw 提前退出:
class Counter{ constructor(limit){ this.limit=limit; } // 这就叫实现了迭代器接口,它返回一个迭代器对象,next是其方法 [Symbol.iterator](){ let count=1, limit=this.limit; return { next(){ if(count<=limit){ return {done:false,value:count++}; }else{ return {done:true,value:undefined}; } }, return(){ console.log('提前退出'); return {done:true}; } } } } let counter = new Counter(3); for(let i of counter){ if(i>2){ break; } console.log(i); } // 1 2 提前退出
如果迭代器没有关闭,还可以继续从上次离开的地方开始迭代:
let a = [1,2,3,4,5]; let iterator = a[Symbol.iterator](); // 返回迭代器 for(let i of iterator){ console.log(i); if(i>2){ break; } } // 1 2 3 for(let i of iterator){ console.log(i); } // 4 5
数组的迭代器是不能关闭的,即使给它增加一个 return 方法也不行。
二、生成器
使用生成器可以拥有一个函数块内暂停和恢复代码执行的能力。比如实现生成器可以自定义迭代器和实现协程... 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行的状态,生成器对象也实现了 Iterator 接口,因此具有next方法。调用这个方法会让生成器开始或恢复执行:
// 生成器函数 function *generatorFn(){ console.log('foobar'); } let generatorObj = generatorFn() // 返回生成器对象,不会执行函数 generatorObj.next() // 调用next()方法开始执行,打印出foobar
yield 关键字可以让生成器停止执行,next方法可以让生成器恢复执行。yield 关键字类似中间返回语句,通过 yield 关键字退出的生成器函数会处在 done: false 状态,通过 return 关键字退出的生成器函数
会处于 done:true 状态:
// 生成器函数 function *generatorFn(){ yield '1'; yield '2'; return '3'; } let generatorObj = generatorFn() // 返回生成器对象,不会执行函数 generatorObj.next() // { done:false,value:'1' } generatorObj.next() // { done:false,value:'2' } generatorObj.next() // { done:true,value:'3' }
生成器作用:
- 可以用来作为可迭代对象(因生成器对象实现了迭代器接口),注意下面代码中for循环调用了生成器函数,返回了生成器对象,for-of会自动调用其迭代器方法next:
function *generatorFn(){ yield '1'; yield '2'; yield '3'; } for(const x of generatorFn()){ console.log(x) // 1 2 3 }
上面代码也可改为 yield 加星号*,它可以将可迭代对象序列化一连串可以单独产出的值:
// 生成器函数 function *generatorFn(){ yield* [1,2,3]; } for(const x of generatorFn()){ console.log(x) // 1 2 3 }
- 生成器作为默认迭代器
因为生成器对象实现了迭代器接口,因此可以作为默认迭代器,下面是一个例子,使用 for-of 会调用 f 对象的 [Symbol.iterator] 函数,返回一个生成器对象:
class Foo{ connstructor(){ this.values=[1,2,3]; } *[Symbol.iterator](){ yield* this.values; } } const f = new Foo(); for(const x of f){ console.log(x); // 1 2 3 }
和迭代器一样,生成器也可以提前终止。它默认有两个方法: return() 和 throw() ,调用这两个方法都可以强制生成器进入关闭状态。提供给 return() 方法的参数,就是终止迭代器对象的值:
function *generatorFn(){ for(const x of [1,2,3]){ yield x; } } const generatorObj = generatorFn(); console.log(generatorObj.next()); // {done:false,value:1} console.log(generatorObj.return()); // {done:true,value:4} // 关闭之后再次调用 next console.log(generatorObj.next()); /// {done:true,value:undefined}
for-of 循环会忽略状态为 done:true 的内部的 value。
throw方法会在往生成器对象中注入一个错误,如果错误未被处理,生成器就会关闭,若生成器函数内部进行了处理,那么生成器就不会被关闭,而且还可以恢复执行:
// 生成器函数内部没有进行错误处理: function *generatorFn(){ for(const x of [1,2,3]){ yield x; } } const generatorObj = generatorFn(); try{ g.throw('foo'); }catch(e){ console.log(e) // foo } console.log(generatorObj) // 生成器对象此时被关闭 // 生成器函数内部进行了错误处理: function *generatorFn(){ for(const x of [1,2,3]){ try{ yield x; }catch(e){} } } const generatorObj = generatorFn(); console.log(generatorObj.next()) // {done:false,value:1} generatorObj.throw('error'); console.log(generatorObj.next()) // {done:false,value:3}
浙公网安备 33010602011771号