前言
对象中的[Symbol.iterator]
属性,一般用来作为对象默认的迭代方法,当它返回迭代器对象时,可以帮助我们创建对象的可迭代方法。
Generator
是es6引入的一种特殊函数,它叫做生成器函数,调用它可以得到一个迭代器对象。
我们从上文可以发现,一个需要返回迭代器对象,一个可以得到迭代器对象,必然可以合作,这就是有趣的地方。
今天我们就来谈谈它们是什么,怎么用。
迭代器对象
满足两点:
- 包含
next
函数。 next
函数返回一个包含value
或done
属性的对象。(done
为Boolean
类型)
const inter = {
x: 1,
next() {
return this.x <= 3 ? {value: this.x++} : {done: true}
}
}
[Symbol.iterator]
[Symbol.iterator]
是什么呢?它是一个对象的方法,这个方法很特殊,是默认的对象迭代方法。
当它内部返回一个迭代器对象时,该对象就可以迭代了。
每当我们使用for...of
或者拓展操作符...
遍历该对象时,进行的就是迭代操作。
我们用上文的迭代器当例子。
const inter = {
x: 1,
next() {
return this.x <= 3 ? {value: this.x++} : {done: true}
}
}
const test = {
[Symbol.iterator]() {
return inter
}
}
console.log(...test) // 1 2 3
// 没有[Symbol.iterator]方法的test2,不可以迭代
const test2 = {}
console.log(...test2) // 报错: Found non-callable @@iterator
一些原本就可以遍历的对象(比如数组对象)存在自己原来默认的
[Symbol.iterator]
方法(所以平时我们默认就可以对它们进行迭代操作),当然,你可以进行重写该方法修改迭代的结果。
一些不存在该方法的对象默认是不能够用
for...of
或者拓展操作符...
遍历它的,你可以通过自己主动在对象上添加[Symbol.iterator]
方法达到让该对象可以进行遍历的目的。
另外,为了方便起见,我们也会让迭代器对象加上[Symbol.iterator]
函数返回自己(因为自己本身就是迭代器对象),让迭代器对象本身变得可以迭代。
const test = {
[Symbol.iterator]() {
return {
x: 1,
next() {
return this.x <= 3 ? {value: this.x++} : {done: true}
},
[Symbol.iterator]() {
return this
}
}
}
}
console.log(...test) // 1 2 3
console.log(...test[Symbol.iterator]()) // 1 2 3
Generator
Generator
是es6的知识,它叫做生成器函数,只要在函数声明使用function
时改成function*
符号,一个函数就成为了Generator
函数。
而且正因为必须使用
function*
,所以箭头函数不可以作为Generator
函数
function* generator() {
yield 1
yield 2
yield 3
}
const gen = generator()
console.log(gen) // Object [Generator] {}
console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: 2, done: false }
console.log(gen.next()) // { value: 3, done: false }
console.log(gen.next()) // { value: undefined, done: true }
我们调用生成器可以得到一个迭代器对象,怎么证明?
因为在Generator
函数中,我们可以用yield
声明迭代节点,函数会返回一个Generator
对象Object [Generator] {}
,这个对象上存在方法next
,调用next
可以不停迭代节点对象出来。
节点对象中包含了value
就是迭代节点的值,done
表示迭代是否结束。
以上完全符合迭代器对象的样子。
另外,要知道的是,Generator
返回的迭代器对象本身实现了[Symbol.iterator]
方法,那么我们自然可以遍历它,遍历它可以通过for...of
或者拓展操作符...
。
function* generator() {
yield 1
yield 2
yield 3
}
const gen1 = generator()
const gen2 = generator()
for (let i of gen1) {
console.log(i) // 1 2 3
}
console.log(...gen2) // 1 2 3
在Generator
函数内我们也可以用一些遍历的方法输出迭代节点的值,看起来更美观。
function* generator() {
for (let i = 1; i <= 3; i++) yield i
}
console.log(...generator()) // 1 2 3
把[Symbol.iterator]写成生成器函数
既然生成器的返回结果就是迭代器对象,那只要把[Symbol.iterator]
方法写成生成器方法,不就不用手动返回迭代器对象了吗,方便了很多。
const test = {
x: 3,
* [Symbol.iterator]() {
for (let i = 1; i <= this.x; i++) yield i
}
}
console.log(...test)// 1 2 3
另外class声明的类对象,也可以在其中添加[Symbol.iterator]
方法,达到让其实例对象拥有可遍历能力的目的。
class Fa {
constructor(n) {
this.n = n
}
* [Symbol.iterator]() {
for (let i = 0; i < this.n; i++) yield i
}
}
const fa = new Fa(10)
console.log(...fa) // 0 1 2 3 4 5 6 7 8 9
异步迭代
如果我们要在生成器内部执行异步内容,然后再进行异步迭代,可以使用异步生成器async function*
,然后使用for await
异步迭代。
async function* clock() {
for (let i = 0; i < 3; i++) {
// 模拟异步任务,1秒后才能得到新的迭代数据
let count = await new Promise((resolve, reject) => setTimeout(() => {
resolve(i)
}, 1000))
yield count
}
}
(async function list() {
// for await 也要再在异步函数中执行
for await (let i of clock()) {
console.log(i) // 0..一秒后..1..一秒后..2
}
})()
尾言
如果有任何错误或者建议,欢迎指出,我会及时修改,如果文章对你有帮助的话,欢迎点赞收藏,感谢~