生成器函数
生成器函数就是在普通函数的 function
关键字后面加个星号(*
)
function* generator() {
yield 1
yield 2
yield 3
}
调用生成器函数,返回的就是一个迭代器(iterator)。
const iterator = generator()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
迭代器就是部署了 next 方法一个对象,每次调用就会返回一个包含 value
和 done
属性的对象:value
表示当前遍历值,done
则表示遍历时候结束。
for-of 循环
for..of 循环是用来遍历可迭代对象(iterable)的。语法如下:
for (variable of iterable) {
// statements
}
所谓的可迭代对象就是部署了 [Symbol.iteraor]
属性的对象,这个属性是个方法。调用这个方法就能得到迭代器,因此 [Symbol.iteraor]
还被称为“迭代器生成函数”。
const arr = ['a', 'b', 'c']
// (1) 使用 for-of 循环
for (const element of arr) {
console.log(element)
}
// (2) 手动循环
let iterator = arr[Symbol.iterator]()
while (true) {
let result = iterator.next()
if (result.done) break
console.log(result.value)
}
(1)
处的 for-of
循环,内部的执行逻辑如 (2)
处代码反映的这样。最终的输出结果为 a
-> b
-> c
。
for-of 与生成器函数
你可能不知道的是:调用生成器函数的结果也能被 for-of
循环遍历。
还是以上面的 generator()
为例:
for (const element of generator()) {
console.log(element)
}
// 1 -> 2 -> 3
果然可以。但我不知道看后的大家有没有疑问,我倒是有。
generator()
的结果不是个迭代器吗,为什么也能被 for-of
调用?莫非 for-of
不仅对可迭代对象,也能直接对迭代器遍历?
我们来试试。
for (const element of arr[Symbol.iterator]()) {
console.log(element)
}
// a -> b -> c
arr[Symbol.iterator]()
返回的是个迭代器,看到确实可以被 for-of
遍历。那么就能说明 for-of
也能对迭代器做遍历吗?
for-of 只能遍历可迭代对象
我们来看下,arr[Symbol.iterator]()
的结构吧。
- 支持
next
方法说明是个迭代器对象。 - 同时原型链上继承了一个
[Symbol.iterator]
属性……啊,原来还是个可迭代对象啊。
数组的 arr[Symbol.iterator]()
的返回对象:既是迭代器,也是可迭代对象。这就有点问题了,因为这里起作用的可能还是 [Symbol.iterator]
属性(按 for-of
的语法来说,是这样的)。
为了充分证明,咱们再用普通对象改装成的可迭代对象,证明一下:
let range = {}
range[Symbol.iterator] = function() {
return {
current: 1,
last: 3,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ }
} else {
return { done: true }
}
}
}
}
我们先用 for-of
遍历一下:
for (let item of range) { console.log(item) } // 1 -> 2 -> 3
OK,没问题。下面进入关键一步了,对 range[Symbol.iterator]()
的结果遍历。
for (let item of range[Symbol.iterator]()) { console.log(item) }
// TypeError: range[Symbol.iterator] is not a function or its return value is not iterable
报错啦!range[Symbol.iterator]
返回的要是个可迭代对象才行。
即是说 arr[Symbol.iterator]()
之所以能够遍历,不是因为它是个迭代器,而是因为它是个可迭代对象。
如果再进一步,我们还能发现一个有趣的地方:
let iterator = arr[Symbol.iterator]()
iterator[Symbol.iterator]() === iterator // true
arr[Symbol.iterator]()
是个可迭代对象,我们对它调用 [Symbol.iterator]
,发现结果与自身相等!这也是为什么前面会给我们造成 for-of
也能遍历迭代器假象的原因。
生成器函数返回值还是个可迭代对象
再回到前面生成器函数的例子:
function* generator() {
yield 1
yield 2
yield 3
}
for (const element of generator()) {
console.log(element)
}
// 1 -> 2 -> 3
generator()
的返回结果是个迭代器对象,但这个对象能被 for-of
循环,绝对是因为它同时还是个可迭代对象!
我们验证下:
果然是的。同样的:
let iterator = generator()
iterator[Symbol.iterator]() === iterator // true
啊哈,同为可迭代对象的迭代器,调用 [Symbol.iterator]
后,结果与自身相等(再摸一遍脑门)——这就是为什么,前面我们会有 for-of
也能遍历迭代器幻象的原因!
使用生成器函数创建可迭代对象
有一个有趣的用例,我们改下下上面的 range
对象:
const range = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
for (let value of range) {
console.log(value)
}
// 1 -> 2 -> 3
这里使用的是“生成器函数的返回值是迭代器”这一特点。
总结
- 调用生成器函数返回的结果,是个迭代器对象(具有
next
方法)。同时, - 这个迭代器还是个可迭代对象。这就是为什么
generator()
的结果可以被for-of
遍历的原因!
参考链接
(正文完)
广告时间(长期有效)
我有一位好朋友开了一间猫舍,在此帮她宣传一下。现在猫舍里养的都是布偶猫。如果你也是个爱猫人士并且有需要的话,不妨扫一扫她的【闲鱼】二维码。不买也不要紧,看看也行。
(完)