背景
遍历数组的时候,我相信大多数人已经用上ES6的for...of语法了:
| 12
 3
 4
 5
 6
 
 | const arr = [1, 2, 3]
 for (const item of arr) {
 console.log(item)
 }
 
 
 | 
那么我们能不能让自己创建的对象也支持for...of呢? 答案自然是可以的,这么好用的语法不给一个自定义支持简直天理难容。
引入
在Python中,有一个好用的range对象,它可以让我们这样写代码:
| 12
 3
 4
 5
 6
 
 | for i in range(5):print(i)
 
 
 print(list(range(5)))
 
 
 | 
我们这里要借助迭代器和生成器实现的就是这个东西,实际上range在Python中也是通过迭代器的方式实现的。
同步迭代器
既然这么说了肯定有异步的迭代器,不过不着急,我们等会再讨论异步的问题,先上代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | function range(end) {let start = 0
 
 const iterator = {
 next() {
 if (start === end) {
 return { done: true }
 }
 return { value: start++, done: false }
 }
 }
 
 return {
 [Symbol.iterator]: () => iterator
 }
 }
 
 for (const i of range(5)) {
 console.log(i)
 }
 
 
 | 
我们定义了一个range函数,它返回一个可迭代对象。
详细的内容请访问MDN查阅,我这里做一个总结:
- 可迭代对象需要有一个Symbol.iterator函数属性,返回迭代器对象。
- 迭代器对象需要有一个next函数属性,返回迭代结果。
- 迭代结果需要有value属性和done属性,这个看名字就知道什么意思了。
所以说,根据这些,我们可以想到一个简化代码的思路,就是让range返回的对象本身也是一个迭代器,当调用Symbol.iterator时返回this:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | function range(end) {let start = 0
 return {
 next() {
 if (start === end) {
 return { done: true }
 }
 return { value: start++, done: false }
 },
 
 [Symbol.iterator]() {
 return this
 }
 }
 }
 
 for (const i of range(5)) {
 console.log(i)
 }
 
 
 | 
已经优化了很多了,但是这样还是不够简练,于是引出我们下文要说的内容。
同步生成器
我们可以发现只是实现一个简单的数字范围生成器就需要10多行代码,这显然是有点麻烦的,于是es6为我们提供了一个东西,它就是生成器:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | function* range(end) {for (let i = 0; i < end; i++) {
 yield i
 }
 }
 
 for (const i of range(5)) {
 console.log(i)
 }
 
 
 | 
现在代码简单多了,然后我们看一下range函数返回了什么东西:
| 12
 3
 4
 5
 6
 7
 
 | const r = range(5)console.log(r)
 
 
 const itor = r[Symbol.iterator]()
 console.log(r === itor)
 
 
 | 
和我们自己实现的时候类似,它调用Symbol.iterator时返回的是this。
相关语法
下面给出与迭代器和生成器有关的语法。
for…of语法
遍历元素值:
| 12
 3
 4
 5
 6
 
 | const arr = [1, 2]
 for (const i of arr) {
 console.log(i)
 }
 
 
 | 
看到这里我们也可以大致模拟一下for...of内部的执行原理了:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | function forOf(obj, action) {const itor = obj[Symbol.iterator]()
 
 while (true) {
 const res = itor.next()
 
 if (res.done) {
 break
 }
 
 action(res.value)
 }
 }
 
 forOf([1, 2, 3], console.log)
 
 
 | 
for await…of语法
在开发过程中,AJAX请求返回的Promise对象是很常见的。
我们用setTimeout模拟异步请求,有下列代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | function asyncValue(value) {return new Promise(resolve => {
 setTimeout(() => resolve(value), value * 100)
 })
 }
 
 
 (async() => {
 const arr = [2, 4, 5].map(asyncValue)
 
 for await (const i of arr) {
 console.log(i)
 }
 
 })()
 
 | 
用for await...of语法可以来遍历一个Promise对象组成的数组。
展开语法
不管是迭代器生成器,都支持es6的展开语法:
| 12
 3
 4
 5
 6
 7
 8
 
 | function* values() {yield 1
 yield 2
 yield 3
 }
 
 console.log([...values()])
 
 
 | 
不做过多的演示了,只需知道展开语法也利用了迭代器生成器即可。
异步迭代器
接下来,我们继续回到range对象上,上面的相关语法只是起一个过渡作用。
给对象一个Symbol.asyncIterator属性,支持它被for await...of迭代:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | function asyncValue(value) {return new Promise(resolve => {
 setTimeout(() => resolve(value), value * 100)
 })
 }
 
 function asyncRange(end) {
 let start = 0
 
 return {
 async next() {
 if (start === end) {
 return { done: true }
 }
 
 const value = await asyncValue(start++)
 return { value, done: false }
 },
 
 [Symbol.asyncIterator]() {
 return this
 }
 }
 }
 
 (async() => {
 for await (const i of asyncRange(5)) {
 console.log(i)
 }
 
 })()
 
 | 
异步生成器
就是加一个async关键字:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | function asyncValue(value) {return new Promise(resolve => {
 setTimeout(() => resolve(value), value * 100)
 })
 }
 
 async function* asyncRange(end) {
 for (let i = 0; i < end; i++) {
 yield await asyncValue(i)
 }
 }
 
 (async() => {
 for await (const i of asyncRange(5)) {
 console.log(i)
 }
 
 })()
 
 |