迭代器与生成器
众所周知, 数组是可以迭代的, 一般的对象是不能迭代的. 比如说
js
const arr = [1]
for (const i of arr) {
console.log(i)
}
const obj = { 0: 1 }
for (const i of obj) {
// 报错!
console.log(i)
}
下面那个会报错
obj is not iterable, obj 不可迭代.
但是众所周知啊, js 的数组也只是一个对象, 同样是对象, 为什么数组就可以迭代?
为什么可迭代?
查询 MDN 可以知道, 数组实现了一个迭代器的东西, 只要一个对象的 Symbol.iterator 是一个函数, 那它就是一个可迭代对象.
可以执行一下这个代码
js
const arr = [1, 2]
console.log(arr[Symbol.iterator])
就会发现打印出一个函数.
ƒ values() { [native code] }
执行一下就得到迭代器了.
js
const arr = [1, 2]
const iter = arr[Symbol.iterator]()
console.log(iter)
可以看到 iter 里有一个 next 方法, 调用一下
js
const arr = [1, 2]
const iter = arr[Symbol.iterator]()
console.log(iter.next())
console.log(iter.next())
console.log(iter.next())
打印出
js
{
"value": 1,
"done": false
}
{
"value": 2,
"done": false
}
{
"value":undefined,
"done": true
}
我们对数组解构的时候实际上就是这样子.
js
const arr = [1, 2];
const [a, b] = arr;
// 等价于
const iter = arr[Symbol.iterator]();
const a = iter.next().value;
const b = iter.next().value;
让普通对象也可迭代
对于我们的普通对象, 也可以把它变成可迭代对象, 只要实现了 Symbol.iterator.
js
const obj = {
a: 1,
b: 2,
[Symbol.iterator]() {
let index = 0
const values = Object.values(this)
return {
next() {
if (index < values.length) {
const value = values[index]
index++
return {
value,
done: false,
}
}
return {
value: undefined,
done: true,
}
},
}
},
}
使用生成器 yield
在 ES6 中, 有一个语法糖可以快速写出迭代器.
js
const obj = {
* [Symbol.iterator]() {
yield 1
yield 2
yield 3
},
}
const [a, b, c] = obj
console.log(a, b, c)
打印出来是 1 2 3
yield
相当于暂时返回这个东西, 然后停止运行. 之后再调用继续执行. 如果没了就返回 undefined 和 done:true 了. 如果要使用 yield 的话, 需要给函数加个*.
js
function* foo() {
yield 1
yield 2
}
const iter = foo()
console.log(iter.next().value) // 1
console.log(iter.next().value) // 2
console.log(iter.next().value) // undefined
在 next 的时候也可以传递参数.
js
function* foo() {
let value = 0
let step
while (true) {
step = yield value++
if (step) {
value += step
}
}
}
const iter = foo()
console.log(iter.next().value) // 0
console.log(iter.next(1).value) // 2
第一次给 next 传参会被无视掉.
yield*
yield*会委托给其它可迭代对象或者迭代器.
js
function* g1() {
yield* [1, 2]
}
function* g2() {
yield 0
yield* g1()
yield 3
}
异步的迭代器
js
async function* asyncIter() {
yield 1
yield new Promise((res) => {
setTimeout(() => {
res(2)
}, 1000)
})
yield 3
}
(async () => {
const iter = asyncIter()
for await (const i of iter) {
console.log(i)
}
})()