- 迭代 iteration , "重复","再来"。
- 按顺序反复多次执行一段程序。
1、理解迭代
- 数组可以通过递增索引遍历,但并不理想
- 迭代之前要事先知道如何使用数据结构
- 遍历顺序并不是数据结构固有的
- ES5新增Array.prototype.forEach()
- 解决了单独计量索引和通过数组对象取得值问题
- 但没办法标识迭代何时终止,只适用于数组。
- 迭代器模式:开发者无须实现知道如何迭代就能实现迭代操作,ES6后开始支持。
2、迭代器模式
- 迭代器模式描述一个方案,即可以把有些结构称为“可迭代对象(iterable)”,因为他们实现了正式的Iterabel接口,而且可以通过迭代器Iterator消费。
- 可迭代对象可理解为 数组或集合这样的集合类型的对象。包含元素有限,都具有无歧义的遍历顺序。
- 可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构,该循环中生成的值是暂时性的,但循环本身是在执行迭代。计数循环和数组都具有可迭代对象的行为。
- 临时性可迭代对象可以实现为生成器。
- 任何实现Iterable接口的数据结构都能被实现Iterator接口的结构“消费”。迭代器是按需创建的一次性对象。每个迭代器会关联一个可迭代对象,而迭代器会暴露迭代器关联可迭代对象的API。迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值。
1、可迭代协议
-
实现Iterable接口s必须具备两种能力
- 支持迭代的自我识别功能
- 创建实现Iterator接口的对象的能力
-
必须暴露一个属性作为“默认迭代器”
-
这个属性必须使用特殊的Symbol.iterator作为键
-
这个默认迭代器属性必须引用一个迭代器工厂函数
-
调用这个工厂函数必须返回一个新迭代器。
-
很多内置类型都实现了Iterable接口,检查是否存在默认迭代器属性可以暴露这个工厂函数
let num = 1 let obj = {} // 没内置 num[Symbol.iterator] // undefined obj[Symbol.iterator] // undefined let str = 'abc'; let arr = ['a', 'b', 'c']; let map = new Map() .set('a', 1) .set('b', 2) .set('c', 3); let set = new Set() .add('a') .add('b') .add('c'); let els = document.querySelectorAll('div'); // 这些类型都实现了迭代器工厂函数 // f values() { [native code] } str[Symbol.iterator] arr[Symbol.iterator] map[Symbol.iterator] set[Symbol.iterator] els[Symbol.iterator] // 调用这个工厂函数会生成一个迭代器 str[Symbol.iterator](); // StringIterator {} arr[Symbol.iterator](); // ArrayIterator {} map[Symbol.iterator](); // MapIterator {} set[Symbol.iterator](); // SetIterator {} els[Symbol.iterator](); // ArrayIterator {} -
实际不需要显式调用这个工厂函数生成迭代器,可接收迭代对象的原生语言特征:
- for-of 循环
- 数组解构
- 扩展操作符
- Array.from()
- 创建集合
- 创建映射
- Promise.all() 接收有期约组成的可迭代对象
- Promise.race() 接收有期约组成的可迭代对象
- yield* 操作符,在生成器中使用
-
如果对象原型链上的父类实现了Iterable接口,可继承
class FooArray extends Array {} let fooArr = new FooArray('foo','bar','baz') for(let el of fooArr){ ... }
2、迭代器协议
-
迭代器是一种一次性使用的对象,用于迭代与其关联的可迭代对象。
-
next() 在迭代对象中遍历数据
-
每次调用next() 都会返回一个IteratorResult对象
- 包含迭代器返回的下一个值
-
如果不调用next() 则无法知道迭代器当前位置
-
IteratorResult对象包含两个属性
- done 布尔值 表示是否还可以调用next() 取得下一个值
- value包含可迭代对象的下一个值(done为false),或者undefined(done为true)
// 可迭代对象 let arr = ['foo','bar'] // 迭代器工厂函数 arr[Symbol.iterator]//f values(){[native code]} // 迭代器 let iter = arr[Symbol.iterator]() iter // ArrayIterator() //执行迭代 iter.next() // {done: false, value: 'foo'} iter.next() // {done: false, value: 'bar'} iter.next() // {done: true, value: undefined} -
只要迭代器达到
done: true状态,后续调用next() 都一直返回相同的值了。 -
每个迭代器都表示对可迭代对象的一次性有序遍历,不同迭代器实例之间没有联系。
-
迭代器不与某个时刻的快照绑定,仅仅是记录遍历可迭代对象的历程,如果期间修改了,迭代器也会变化
let arr = [1,2] let iter = arr[Symbol.iteator]() iter.next() // {done: false, value: 1} arr.splice(1,0,0) iter.next() // {done: false, value: 0} iter.next() // {done: false, value: 2} iter.next() // {done: true, value: undefined} -
迭代器维护着一个指向可迭代对象的引用,因此迭代器会阻止垃圾回收程序回收可迭代对象。
-
“迭代器”,可以指通用的迭代,也可指借口,可以指正式的迭代器类型。
// 这个类实现了可迭代接口(Iterable) // 调用默认的迭代器工厂函数会返回 // 一个实现迭代器接口(Iterator)的迭代器对象 class Foo { [Symbol.iterator]() { return { next() { return {done: false, value: "foo"} } } } } let f = new Foo() f[Symbol.iteator]() // { next: f() {} } // Array类型实现了可迭代接口(Iterable) // 调用Array类型的默认迭代器工厂函数 // 会创建一个ArrayIterator实例 let a = new Array() a[Symbol.iterator]() // Array Iterator{}3、自定义迭代器
-
与Iterable接口类似,任何实现Iterator接口的对象都可以作为迭代器使用。
// 每个实例只能迭代一次 class Counter { constructor(limit) { this.count = 1 this.limit = limit } next() { if(this.count <= this.limit) { return {done: false, value: this.count++} }else { return {done: true, value: undefined} } } [Symbol.iterator]() { return this } } let counter = new Counter(3) for(let i of counter) { ... } -
为了让一个可迭代对象创建多个迭代器,必须每创建一个迭代器就对应一个新计数器。
class Counter { constructor(limit){ this.limit = limit } [Symbol.iterator]() { let count = 1; let 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) { ... } for(let i of counter) { ... } -
每个以这种方式创建的迭代器也实现了Iterable接口。
-
Symbol.iterator 属性引用的工厂函数会返回相同的迭代器
let arr = [1,2,3] let iter1 = arr[Symbol.iterator]() let iter2 = iter1[Symbol.iterator]() iter1 === iter2 -
因为每个迭代器实现了Iterable接口,所以可以用在任何期待可迭代对象的地方。
4、提前终止迭代器
-
return() 方法用于指定在迭代器提前关闭时执行的逻辑。
-
执行迭代的结构在想让迭代器知道他不想遍历到可迭代对象耗尽时,就可以“关闭”迭代器。
- for-of 通过break,continue,return,throw提前退出
- 解构操作并未消费所有值
-
return() 方法必须返回一个有效的IteratorResult对象。
class Counter{ constructor(limit) { this.limit = limit } [Symbol.iterator]() { let count = 1; let limit = this.limit; return { next() { if(count <= limit) { return {done: false, value: count++} }else { return {done: true} } }, return() { console.log('Exiting early') return {done: true} } } } } let counter1 = new Counter(5) for(let i of counter1) { if(i > 2) { break } console.log(i) } let counter2 = new Counter(5) try{ for(let i of counter2) { if(i > 2) { throw 'err' } console.log(i) } }catch(e) {} let counter3 = new Counter(5) let [a, b] = counter3 -
若迭代器没有关闭,则可以继续从上次离开的地方迭代
-
因为return() 方法可选,所以并非所有迭代器都是可以关闭的。可以测试return属性是不是函数对象。不过仅仅给不可关闭的迭代器增加这个方法不能让它变成可关闭的,因为调用return() 不会强制迭代器进入关闭状态。但return还是会调用
let a = [1,2,3,4,5] let iter = a[Symbol.iterator]() iter.return = function () { console.log('Exiting early') return {done: true} } for(let i of iter) { console.log(i) if(i > 2){ break; } } // 1 // 2 // 3 // 'Exiting early' for(let i of iter) { console.log(i) } // 4 // 53、生成器
-
生成器拥有在一个函数块内暂停和恢复代码执行的能力。
-
这中能里可以自定义迭代器和实现协程
1、生成器基础
-
生成器的形式是一个函数,函数名前面一个星号(*)表示他是一个生成器。
-
可以定义函数的地方,都可以定义生成器
// 生成器函数声明 function* generatorFn() {} // 生成器函数表达式 let generatorFn = function* () {} // 作为对象字面量方法的生成器函数 let foo = { * generationFn() {} } // 作为类实例方法的生成器函数 class Foo { * generationFn() {} } // 作为静态方法的生成器函数 class Foo { static * generationFn() {} }- 箭头函数不能用来定义生成器函数
-
标识生成器函数的星号不受两侧空格影响
// 等价的 function* generatorFnA() {} function *generatorFnB() {} function * generatorFnC() {} class Foo { *generatorFnD() {} * genreatorFnF() {} } -
调用生成器函数会产生一个生成器对象。
-
一开始处于暂停状态
-
与迭代器相似,也实现了Iterator接口,有next()方法
-
调用这个方法会让生成器开始或恢复执行
function* generatorFn() {} const g = generatorFn() g // generatorFn {<suspended>} g.next // f next() { [native code] } -
next()方法的返回值类似于迭代器,有一个 done 属性和一个 value 属性。
-
函数体为空的生成器函数中间不会停留,调用一次 next()就会让生成器到达 done: true 状态。
function* generatorFn() {} let generatorObject = generatorFn(); generatorObject; // generatorFn {<suspended>} generatorObject.next(); // { done: true, value: undefined } -
value 属性是生成器函数的返回值,默认值为 undefined,可以通过生成器函数的返回值指定
function* generatorFn() { return 'foo'; } let generatorObject = generatorFn(); generatorObject; // generatorFn {<suspended>} generatorObject.next(); // { done: true, value: 'foo' } -
生成器函数只会在初次调用 next()方法后开始执行
function* generatorFn() { console.log('foobar'); } // 初次调用生成器函数并不会打印日志 let generatorObject = generatorFn(); generatorObject.next(); // foobar -
生成器对象实现了 Iterable 接口,它们默认的迭代器是自引用的
function* generatorFn() {} generatorFn; // f* generatorFn() {} generatorFn()[Symbol.iterator]; // f [Symbol.iterator]() {native code} generatorFn(); // generatorFn {<suspended>} generatorFn()[Symbol.iterator](); // generatorFn {<suspended>} const g = generatorFn(); g === g[Symbol.iterator](); // true
-
2、通过yield中断执行
-
yield可以让生成器停止和开始执行。
-
生成器函数在遇到yield关键字之前会正常执行。
-
遇到这个关键字的时执行会停止,函数作用域的状态保留。
-
停止执行的生成器函数只能通过在生成器对象上调用next() 方法恢复执行。
function* generatorFn() { yield; } let generatorObject = generatorFn(); generatorObject.next() //{done: false, value: undefined} generatorObject.next() //{done: true, value: undefined} -
yield像函数的中间返回语句
-
生成的值会出现在next()方法返回的对象中
-
通过yield关键字推出的生成器函数会处在done: false状态
-
通过return 退出的会处于 done : true 状态
function* generatorFn() { yield 'foo'; yield 'bar'; return 'baz'; } let generatorObject = generatorFn(); console.log(generatorObject.next()); // { done: false, value: 'foo' } console.log(generatorObject.next()); // { done: false, value: 'bar' } console.log(generatorObject.next()); // { done: true, value: 'baz' } -
内部执行流程会针对每个生成器对象区分作用域,不会互相影响。
-
yield只能在生成器函数内部使用,在其他地方会抛错,必须直接定位于生成器函数定义中,出现在嵌套的非生成器函数中会报错。
// 有效 function* validGeneratorFn() { yield; } // 无效 function* invalidGeneratorFnA() { function a() { yield; } } // 无效 function* invalidGeneratorFnB() { const b = () => { yield; } } // 无效 function* invalidGeneratorFnC() { (() => { yield; })(); }
1、生成器对象作为可迭代对象
-
显式调用next() 用处不大,当成可迭代对象更方便。
function* generatorFn() { yield 1; yield 2; yield 3; } for(const x of generatorFn()) { console.log(x) } // 1 // 2 // 3 -
执行指定次数
function* nTimes(n) { while(n--){ yield } } for(let _ of nTimes(2)) { console.lgo('foo') } // foo // foo
2、使用yield实现输入输出
-
作为函数中间参数使用,上一次让生成器暂停的yield关键字会接收到next() 方法的第一个值。
-
第一次调用的next()传入的值不会使用,因为第一次是为了开始执行生成器函数。
function* generatorFn(initial) { console.log(initial) console.log(yield) console.log(yield) } let generatorObject = generatorFn('foo') generatorObject.next('bar') // foo generatorObject.next('baz') // baz generatorObject.next('qux') // qux -
yield 可以同时用于输入输出
function* generatorFn() { return yield 'foo' } let generatorObject = generatorFn() generatorObject.next() //{done: false, value: 'foo'} generatorObject.next('bar') //{done: true, value: 'bar'} -
函数必须要对整个表达式求值才能确定要返回的值,在遇到yield关键字时暂停执行并计算出要产生的值foo,下一次调研next()传入了bar,作为交给同一个yield的值。然后被确定为本次生成器函数要返回的值。
-
yield并非只能用一次
function* genreatorFn() { for(let i = 0; ++i){ yield i; } } -
根据配置的值迭代相应次数产生迭代的索引。
function* nTimes(n) { for(let i = 0; i < n; i++) { yield i } } // while function* nTimes(n) { let i = 0 while(n--) { yield i++ } } for(let x of nTimes(3)){ console.log(x) } -
可以用来实现范围和填充数组
function* range(start, end) { while(end > start) { yield start++ } } for(const x of range(4,7)){ console.log(x) } // 4 // 5 // 6 function* zeroes(n) { while(n--) { yield 0 } } Array.from(zeroes(8)) //[0,0,0,0,0,0,0,0]
3、产生可迭代对象
-
使用星号增强yield行为,让它能够迭代一个可迭代对象,从而一次产出一个值
function* generatorFn() { for(const x of [1,2,3]){ yield x } } function* generatorFn() { yield* [1,2,3] } -
yield 星号两侧空格不影响
function* generatorFn() { yield* [1,2] yield *[3,4] yield * [5,6] } for(const x of generatorFn()) { console.log(x) } //1-6- yield* 实际上只是讲一个可迭代对象序列化为一连串可以单独产出的值。
-
yield*的值是关联迭代器返回done: true时value的属性,
- 普通迭代器来说是undefined
function* generatorFn() { console.log('iter value:',yield* [1,2,3]) } for(const x of generatorFn()) { console.log('value': x) } // value: 1 // value: 2 // value: 3 // iter value: undefined- 对于生成器函数,这个值是生成器函数返回的值
function* innerGeneratorFn() { yield 'foo' return 'bar' } function* outerGeneratorFn() { console.log('iter value:',yield* innerGeneratorFn()) } for(const x of outerGeneratorFn()) { console.log('value': x) } // value: foo // iter value: bar
4、使用yield*实现递归算法
-
最有用的地方是实现递归操作,生成器产生自身
function* nTimes(n) { if(n > 0){ yield* nTimes(n - 1) yield n-1 } } for(const x of nTimes(3)){ console.log(x) } // 0 // 1 // 2 -
图的实现
class Node { constructor(id) { this.id = id this.neighbors = new Set() } connect(node) { if(node !== this) { this.neighbors.add(node) node.neighbors.add(this) } } } class RandomGraph { constructor(size) { this.nodes = new Set() // 创建节点 for(let i = 0; i < size; i++) { this.nodes.add(new Node(i)) } // 随机连接节点 const threshold = 1 / size for(const x of this.nodes) { for(const y of this.nodes) { if(Math.random() < threshold) { x.connect(y) } } } } print() { for(const node of this.nodes) { const ids = [...node.neighbors] .map(n=>n.id) .join(','); console.log(`${node.id}:${ids}`) } } } const g = new RandomGraph(6) g.print- 图的结构适合递归遍历,生成器函数必须接收一个可迭代对象,产出该对象中的每一个值,并且对每个值进行递归,可以用来测试某个图是否连通。是否没有不可到达的节点(深度优先遍历)
class Node { constructor(id) { ... } connect(node) { ... } } class RandomGraph { constructor(size) { ... } print() { ... } isConnected() { const visitedNodes = new Set() function* traverse(nodes){ for(const node of nodes){ if(!visitedNodes.has(node)){ yield node; yield* traverse(node.neighbors) } } } // 取得集合中第一个节点 const firstNode = this.nodes[Symbol.iterator]().next().value // 使用递归生成器迭代每个节点 for(const node of traverse([firstNode])){ visitedNodes.add(node) } return visitedNodes.size === this.nodes.size } }3、生成器作为默认迭代器
class Foo {
constructor() {
this.value = [1,2,3]
}
* [Symbol.iterator]() {
yield* this.value
}
}
const f = new Foo()
for(const x of f){
console.log(x)
}
- for-of循环调用了默认迭代器(也是生成器函数)并产生了一个生成器对象,这个生成器对象是可迭代的,所以完全可以在迭代中使用。
4、提前终止生成器
-
生成器除了包含next() ,return()外还有一个throw()
function* generatorFn() {} const g = generatorFn(); g // generatorFn {<suspended>} g.next // f next() { [native code] } g.return // f return() { [native code] } g.throw // f throw() { [native code] } -
return()和 throw()方法都可以用于强制生成器进入关闭状态。
1、return()
-
强制生成器进入关闭状态,提供给return() 方法的值,就是终止迭代器对象的值
function* generatorFn() { for(const x of [1,2,3]) { yield x; } } const g = generatorFn() g // generatorFn {<suspended>} g.return(4) // {done: true, value: 4} g // generatorFn {<closed>} -
所有生成器对象都有return方法,只要通过它进入关闭状态,就无法恢复了。后续再调用next() 都会返回done: true状态
-
for-of循环等内置语言会自动忽略状态为done: true 的InteratorObject 内部返回的值。
function* generatorFn() { for(const x of [1,2,3]) { yield x; } } const g = generatorFn() for(const x of g) { if(x > 1) { g.return(4) } console.log(x) } //1 //2
2、throw()
-
throw方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未处理,生成器就会关闭。
function* generatorFn() { for(const x of [1,2,3]) { yield x } } const g = generatorFn() g // generatorFn {<suspended>} try{ g.throw('foo') }catch(e) { console.log(e) // foo } g // generatorFn {<closed>} -
如果生成器函数内部处理了这个错误,生成器就不会被关闭,可以恢复执行,错误处理会跳过对应的yield。
function* generatorFn() { for(const x of [1,2,3]) { try{ yield x }catch(e) {} } } const g = generatorFn() g.next() // {done: false, value: 1} g.throw('foo') // 实际返回 {done: false, vlaue: 2} g.next() // {done: false, value: 3} -
如果生成器对象还没有开始执行,那么调用throw() 抛出的错误不会在函数内部被捕获,因为相当于在函数块外部抛出了错误