序言
什么是 迭代器
? 广义上来讲 迭代器
(iterator
) 有时又称光标
(cursor
) 是程序设计软件的一种 设计模式
, 可在 容器对象
(container
, 例如例如列表、元组或字典) 上遍访的接口, 开发者无需关心容器对象的实现细节, 就可以通过它按照 特定的顺序
访问其中的每个元素, 同时各种语言实现 迭代器
的方式皆不尽同。
一、迭代器协议
JS
中 迭代器协议
定义了一种产生一系列值的 标准方式
, 这一系列的值可以是有限个也可以是无限个, 当值是有限个时, 所有的值都被迭代完毕后, 就会返回一个默认返回值(undefined
)。
1.1 协议的基本约定
只要一个
对象
实现了以下功能的next()
方法, 这个对象
就被称为迭代器
- 无参数或者接受一个参数
- 返回一个符合
IteratorResult
接口的对象, 该对象必须有以下属性:
done
(可选): 布尔值, 表示是否迭代完毕value
(可选): 本次迭代的值, 可以是任何值
如下代码: obj
实现了符合上文提到的约定, 那么该对象就能称之为 迭代器
, 其中 next()
不接收参数, 每次执行都会 按照顺序
返回 特定的值
, 直到迭代结束
const obj = {
i: 0,
next(){
if (this.i < 3) {
return {
done: false,
value: 2 * [this.i++],
}
}
return { done: true, value: undefined }
}
}
console.log('1', obj.next()); // 1 { done: false, value: 0 }
console.log('2', obj.next()); // 2 { done: false, value: 2 }
console.log('3', obj.next()); // 3 { done: false, value: 4 }
console.log('4', obj.next()); // 4 { done: true, value: undefined }
实际上, IteratorResult
接口中, done
和 value
都不是严格要求的, 如果返回没有任何属性的对象, 实际上等价于 { done: false, value: undefined }
const obj = {
i: 0,
next(){
if (this.i < 3) {
this.i++
return {} // 返回空对象, 是被允许的, 等价于 { value: undefined, done: false }
}
return { done: true, value: undefined }
}
}
console.log('1', obj.next()); // 1 {}
console.log('2', obj.next()); // 2 {}
console.log('3', obj.next()); // 3 {}
console.log('4', obj.next()); // 4 { value: undefined, done: true }
总结: 迭代器
其实本质上就是个对象, 只是它实现了特定的协议(约定), 让它能够被叫做 迭代器
, 迭代器
其实就一种 设计模式
, 它在 JS
中的表现形式就是一个对象定义了一个 next()
方法, 方法返回一个具有 value
和 done
属性的对象
1.2 return 方法
协议规定: 迭代器
允许定义一个 return()
方法, 该方法是可选的, 用于提前结束迭代, 当我们调用该方法时, 将会告诉迭代器, 调用者已经完成了迭代, 该方法约定如下:
- 无参数或者接受一个参数
value
, 通常value
作为IteratorResult
接口对象的value
值进行返回- 返回一个符合
IteratorResult
接口的对象, 该对象必须有以下属性:
done
(可选): 布尔值, 表示是否迭代完毕;value
(可选): 本次迭代的值, 可以是任何值;
如下代码: 实现了 return()
方法, 方法的 value
参数将作为 IteratorResult
接口对象的 value
值, 并且 done
将被设置为 true
表示迭代结束
const obj = {
i: 0,
next(){
if (this.i < 3) {
return {
value: 2 * [this.i++],
done: false,
}
}
return { value: undefined, done: true }
},
return(value){
this.i = 3 // 迫使, 迭代结束 (再次执行 next 不会继续迭代)
return { value, done: true }
}
}
console.log('1', obj.next()); // 1 { value: 0, done: false }
console.log('2', obj.return(10)); // 2 { value: 10, done: true }, 提前结束迭代
console.log('3', obj.next()); // 3 { value: undefined, done: true }
1.3 throw 方法
协议规定: 迭代器
允许定义一个 throw()
方法, 该方法是可选的, 用于提前结束迭代, 当我们调用该方法时, 表明 迭代器
的调用者监测到错误, 强制结束迭代, 该方法约定如下:
- 无参数或者接受一个参数
exception
, 并且exception
通常是一个Error
实例- 返回一个符合
IteratorResult
接口的对象, 该对象必须有以下属性:
done
(可选): 布尔值, 表示是否迭代完毕value
(可选): 本次迭代的值, 可以是任何值
如下代码: 实现了 throw()
方法, 参数为 Error
实例, 方法返回IteratorResult
接口对象, 对象中 done
为 true
表示迭代结束
const obj = {
i: 0,
next(){
if (this.i < 3) {
return {
value: 2 * [this.i++],
done: false,
}
}
return { value: undefined, done: true }
},
throw(exception){
this.i = 3 // 迫使, 迭代结束 (再次执行 next 不会继续迭代)
return { value: undefined, done: true }
}
}
console.log('1', obj.next()); // 1 { value: 0, done: false }
console.log('2', obj.throw(new Error('111'))); // 2 { value: undefined, done: true }, 发生错误, 提前结束迭代
console.log('3', obj.next()); // 3 { value: undefined, done: true }
二、可迭代协议
可迭代协议: 在 ES6
中, 允许在对象中通过 Symbol.iterator
属性来定义或定制对象的 迭代行为
, Symbol.iterator
是一个方法, 该方法返回一个 迭代器
, 也只有实现了该协议(规定)的对象才能够被 for...of
给循环遍历
2.1 可迭代对象
实现了 可迭代协议
的对象则被称为 可迭代对象
, 简单来说: 要成为 可迭代对象
, 该对象必须实现 Symbol.iterator
方法, 并且该方法是一个无参数的函数, 其返回值是一个 迭代器
如下代码: 对象 obj
定义了 Symbol.iterator
方法, 该方法返回一个 迭代器
, 那么 obj
就被称为 可迭代对象
const obj = {
[Symbol.iterator](){
let i = 0;
return {
next(){
if (i < 3) {
return { value: i++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
2.2 循环可迭代对象
上文我们知道了如何实现一个 可迭代对象
, 这里如果我们使用 for of
来循环 可迭代对象
, 在执行 for...of
循环时, 会发生哪些事呢?
-
先调用对象的
Symbol.iterator
方法, 生成一个迭代器
-
循环调用
迭代器
的next()
方法, 方法会返回具有value
和done
两个属性的对象,value
表示当前迭代的值,done
则表示是否遍历结束。 -
每次判断
done
是否为true
, 如果是则循环会自动结束
下面是演示代码: 方法
forOf
模拟了循环可迭代对象
的流程, 并且打印出了每次迭代的值
// 可迭代对象
const obj = {
[Symbol.iterator](){
let i = 0;
return {
next(){
if (i < 3) {
return { value: i++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
// 自定义方法: 循环打印迭代对象的值
const forOf = (obj) => {
const iterator = obj[Symbol.iterator]()
let done = false
while (!done) {
const current = iterator.next()
done = current.done
if (!done){
console.log('forOf', current.value)
}
}
}
forOf(obj) // 打印: forOf 0、forOf 1、forOf 2
我们如果使用
for of
来循环迭代对象, 会发现和上面打印结果是一样样滴
const obj = {
[Symbol.iterator](){
let i = 0;
return {
next(){
if (i < 3) {
return { value: i++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
for (const value of obj) {
console.log('forOf', value) // // 打印: forOf 0、forOf 1、forOf 2
}
其实
for...of
只能用于循环可迭代对象
, 当然除了for...of
下面这些语法、方法也都必须要求操作对象是一个可迭代对象
-
for...of
-
展开语法:
const arr = [...rest];
-
解构语法:
const [a, b, c] = arr;
-
Array.from()
-
Map()
-
WeakMap()
-
Set()
-
WeakSet()
-
Promise.all()
-
Promise.allSettled()
-
Promise.race()
-
Promise.any()
-
Array.from()
-
....
2.3 篡改迭代器
已知, 我们可以使用 for...of
循环数组, 但是不能循环 普通对象
, 循环 普通对象
将会提示对象是不可迭代的, 如下代码: for...of
能够正常循环数组、但是不能循环普通对象 obj
const arr = [1, 2, 3]
const obj = {
name: 'moyuanjiun'
}
// 循环数组
for (const value of arr) {
console.log(value); // 1 2 3
}
// 循环普通对象
for (const value of obj) {
console.log(value); // 报错: TypeError: obj is not iterable
}
之所以出现出现这个现象, 其实也好理解: 那是因为数组 原型
上内置了默认的 Symbol.iterator
方法, 实现了 可迭代协议
, 所以它是一个 可迭代对象
; 但是呢, 普通对象是没有内置 Symbol.iterator
方法的, 所以它并 不是
一个 可迭代对象
const arr = [1, 2, 3]
const obj = {
name: 'moyuanjiun'
}
if (arr[Symbol.iterator]) {
console.log('数组是可迭代对象') // 数组是可迭代对象
}
if (!obj[Symbol.iterator]) {
console.log('普通对象是不可迭代对象') // 普通对象是不可迭代对象
}
那么我们如果要 篡改
对象的默认 迭代行为
也很简单, 只需要修改默认的 Symbol.iterator
方法即可, 如下代码: 通过修改 Symbol.iterator
方法, 篡改了数组 arr
的迭代行为, 每次迭代获取的值都会乘以 2
const arr = [1, 2, 3]
arr[Symbol.iterator] = function() {
let index = 0
return {
next: () => {
const ele = this[index++]
if (ele) {
return { value: 2 * ele, done: false }
}
return { value: undefined, done: true }
}
}
}
for (const value of arr) {
console.log(value) // 2 4 6
}
同样的 普通对象
是无法被 迭代
的, 但如果我们设置了 Symbol.iterator
属性就能够让普通对象允许被迭代, 如下代码: 我们为 obj
定义了 Symbol.iterator
方法, 该方法返回一个 迭代器
const obj = {
[Symbol.iterator]() {
let index = 0
return {
next: () => {
if (index < 3) {
return { value: index++, done: false }
}
return { value: undefined, done: true }
}
}
}
}
for (const value of obj) {
console.log(value) // 0 1 2
}
那么问题来了 JS
中常见的数据中, 有哪些数据是默认允许被迭代的呢? 主要有下面几种:
- Array
- string
- Set
- Map
2.4 可迭代迭代器
已知 迭代器
本质上就是个对象, 我们其实很容易使它变为 可迭代对象
, 只需实现 Symbol.iterator
方法, 并返回它的 this
即可, 这时该对象我们可称之为 可迭代迭代器
, 如下代码: obj
为 可迭代迭代器
使用 for...of
就能够直接循环输出
const obj = {
index: 0,
next(){
if (this.index < 3) {
return { value: this.index++, done: false }
}
return { value: undefined, done: true }
},
[Symbol.iterator](){
return this
}
}
for (const value of obj) {
console.log(value) // 0 1 2
}
其实在不实现 可迭代协议
的情况下, 仅实现 迭代器协议
的作用很小, 大部分 迭代器
都实现了 可迭代协议
, 包括后面要讨论的 生成器
, 它返回的就是 可迭代迭代器
三、异步迭代器
异步迭代器
和 迭代器
约定基本一致, 唯一不同的是 异步迭代器
中 next
、return
、throw
等方法返回的 IteratorResult
接口对象中, value
值是一个 Promise
实例, 如下代码所示: obj
是一个 异步迭代器
, next()
方法返回的对象中 value
是一个 Promise
实例
const obj = {
index: 0,
next(){
if (this.index < 3) {
return {
done: false,
// value 为 Promise 实例
value: new Promise(resolve => {
setTimeout(() => resolve(this.index), 1000 * this.index++)
}),
}
}
return { done: true, value: Promise.resolve(undefined) }
}
}
上面例子中, 每次执行 next()
进行迭代取值, 拿到的 value
值是个 Promise
实例, 下面是一个调用 异步迭代器
的简单例子
const obj = {
index: 0,
next(){
if (this.index < 3) {
return {
done: false,
value: new Promise(resolve => {
setTimeout(() => resolve(this.index), 1000 * this.index++)
}),
}
}
return { done: true, value: Promise.resolve(undefined) }
}
}
const run = async () => {
const res1 = await obj.next().value // 0 秒后输出: 1
const res2 = await obj.next().value // 1 秒后输出: 2
const res3 = await obj.next().value // 2 秒后输出: 3
const res4 = await obj.next().value // 3 秒后输出: undefined
}
run()
四、异步可迭代协议
异步可迭代协议
和 可迭代协议
约定基本一致, 只是它是通过 Symbol.asyncIterator
方法来设置
对象的迭代行为, 并且该方法返回的是一个 异步迭代器
, 如下代码: obj
定义了 Symbol.asyncIterator
方法, 实现了 异步可迭代协议
, 我们可以称之为 异步可迭代对象
const obj = {
[Symbol.asyncIterator](){
let index = 0;
return {
next(){
if (index < 3) {
return {
done: false,
value: new Promise(resolve => {
setTimeout(() => resolve(index), 1000 * index++)
}),
}
}
return { done: true, value: Promise.resolve(undefined) }
}
}
}
}
对于 可迭代对象
, 我们可使用 for...of
进行迭代, 那么对于 异步可迭代对象
, 如果使用 for...of
进行迭代是会抛出错误, 因为我们并没有定义 Symbol.iterator
, 它是一个 不可迭代对象
, 如下代码: 我们尝试使用 for...of
来迭代 异步可迭代对象
, 最后会报 obj is not iterable
const obj = {
[Symbol.asyncIterator](){
let index = 0;
return {
next(){
if (index < 3) {
return {
done: false,
value: new Promise(resolve => {
setTimeout(() => resolve(index), 1000 * index++)
}),
}
}
return { done: true, value: Promise.resolve(undefined) }
}
}
}
}
for (const value of obj) { // obj is not iterable
console.log(value)
}
那么我们又该怎么循环 异步可迭代对象
呢?这里可以使用 for await ... of
来循环 异步可迭代对象
, 如下代码: 演示了如何使用 for await ... of
来循环 异步可迭代对象
const obj = {
[Symbol.asyncIterator](){
let index = 0;
return {
next(){
if (index < 3) {
return {
done: false,
value: new Promise(resolve => {
setTimeout(() => resolve(index), 1000 * index++)
}),
}
}
return { done: true, value: Promise.resolve(undefined) }
}
}
}
}
for await (const promise of obj) {
// 这里这里拿到的值是 Promise 实例
const value = await promise
console.log(value) // 立即打印出: 1, 1 秒后打印 2, 2 秒后打印出 3
}
这里卖个关子: 上面例子中, 为什么打印出的是
1
2
3
, 而不是0
1
2
, 欢迎在评论区进行讨论, 也可以进行简单改造, 让它打印出0
1
2
其实循环 异步可迭代对象
和循环 可迭代对象
逻辑基本一致:
-
先调用对象的
Symbol.asyncIterator
方法, 生成一个异步迭代器
-
循环调用
异步迭代器
的next()
方法, 方法会返回具有value
和done
两个属性的对象,value
是一个Promise
实例,done
则表示是否遍历结束 -
每次判断
done
是否为true
, 如果是则循环会自动结束
下面是演示代码: 方法
forAwaitOf
模拟了循环异步可迭代对象
的流程, 并且打印出了每个Promise
实例
const obj = {
[Symbol.asyncIterator](){
let index = 0;
return {
next(){
if (index < 3) {
return {
done: false,
value: new Promise(resolve => {
setTimeout(() => resolve(index), 1000 * index++)
}),
}
}
return { done: true, value: Promise.resolve(undefined) }
}
}
}
}
const forAwaitOf = (obj) => {
const asyncIterator = obj[Symbol.asyncIterator]()
let done = false
while (!done) {
const current = asyncIterator.next()
done = current.done
if (!done){
console.log('forAwaitOf', current.value)
}
}
}
forAwaitOf(obj)
五、生成器
生成器
是一种特殊的 JS
函数, 它使用 function*
关键字来进行定义, 该函数会返回一个 Generator
对象, 该对象是符合 迭代器协议
的, 所以它本质上就是个 迭代器
当我们执行 生成器
函数将会返回一个 迭代器
, 它并不会直接执行函数体里面的代码, 如下代码: 函数里的代码并没有执行, 函数返回一个 Generator
对象, 也是一个 迭代器
function* generator (){
console.log('这段代码不会被执行')
}
const res = generator()
console.log(res) // Object [Generator] {}
在 生成器
中可使用 yield
关键字来暂停代码的执行, yield
执行逻辑如下:
- 调用
生成器
会返回一个迭代器
- 当我们调用
迭代器
的next()
方法将执行函数里面的代码, 执行过程中如果遇到yield
关键词, 函数的执行会被暂停,yield
关键词后面的值
将被作为next()
方法的value
值被返回 - 下一次再次执行
next()
方法时, 代码将从暂停的地方继续执行, 直到再次遇到yield
关键词, 函数的执行又会被暂停,yield
关键词后面的值
作为next()
方法的value
值被返回 - 如此往复, 直到函数内代码全部执行完毕,
next()
方法中done
将被设置为ture
, 整个迭代结束
有如下代码: 创建了一个 生成器
generator
, 并执行 生成器
返回了一个 迭代对象
gen
, 然后依次调用 迭代对象
的 next()
方法
function* generator() {
// 第一次 next 执行下列代码
let a = 1;
let b = 1;
yield a + b
// 第二次 next 执行下列代码
a = 2
b = 3
yield a + b
// 第三次 next 执行下列代码
a = 9
b = 3
console.log('函数体代码执行结束:', a / b)
}
const gen = generator()
const res1 = gen.next() // { value: 2, done: false }
const res2 = gen.next() // { value: 5, done: false }
const res3 = gen.next() // { value: undefined, done: true }
const res4 = gen.next() // { value: undefined, done: true }, 因为循环已经结束, 不会执行任何代码
为每个关键节点添加了 debugger
, 下图是 debug
的一个演示图:
5.1 定义「可迭代对象」
上文中提到, 要想实现一个 可迭代对象
, 只需要遵循 可迭代协议
, 只需要定义出符合要求的 Symbol.iterator
方法即可, 该方法返回一个 迭代器
读到这里你是不是会发现, 可迭代对象
中 Symbol.iterator
方法其实和 生成器
很像, 他们都是一个用于生产 迭代器
的一个方法, 生成器
实际上是一个语法糖, 它的作用其实就是为了让我们更方便、更快速的创建一个 迭代器
下面代码: 我们使用 生成器
来为对象定义 Symbol.iterator
方法, 来实现一个 可迭代对象
, 从代码上也可以发现使用 生成器
可以很容易创建一个 可迭代对象
, 而且理解起来也很简单(每个 yield
后面的值, 就是每次迭代输出的 value
)
const obj = {
*[Symbol.iterator](){
yield 1
yield 2
yield 3
}
}
for (const value of obj) {
console.log(value) // 1 2 3
}
5.2「生成器」返回「可迭代迭代器」
生成器
返回一个 迭代器
, 打印该 迭代器
会发现该对象内部实现了 Symbol.iterator
方法, 也就是说 生成器
返回的 迭代器
是一个 可迭代迭代器
, 如下代码: 直接使用 for...of
循环 生成器
返回的 迭代器
, 会发现能够正常循环的, 因为它是一个 可迭代迭代器
function* generator(length) {
let index = 0
while(index < length) {
yield index
index ++
}
}
// 直接循环生成器产物
for (let value of generator(3)) {
console.log(value); // 0 1 2
}
5.3 生成器传参
既然 生成器
是个函数, 那我们自然就能够为它设置参数, 如下代码: 通过变量 length
设置了 生成器
最终返回的 迭代器
可迭代次数
function* generator(length = 3) {
let index = 0
while (index < length) {
yield index * 2
index++
}
}
const gen = generator(5)
for (const value of gen) {
console.log(value) // 0 2 4 6 8
}
5.4 next() 传参
在这里试想下, 如果我们为 next()
传参那么这个参数将会被怎么调用的呢?
看下面代码, 从执行结果是否能发现到它们的规律呢?用一句话来讲就是: 当前 next()
方法的的参数, 将会作为上一句 yield
关键词的结果赋值给 左侧
的变量, 如下代码: 生成器
中每个 yield
关键词都会返回上一次 yield
的结果
function* generator(data) {
const first = yield data
const second = yield first
const third = yield second
yield third
}
const gen = generator(10)
console.log('1', gen.next()); // 1 { value: 10, done: false }
console.log('2', gen.next(20)); // 2 { value: 20, done: false }
console.log('3', gen.next("moyuanjun")); // 3 { value: 'moyuanjun', done: false }
console.log('4', gen.next({ age: 18 })); //4 { value: { age: 18 }, done: false }
console.log('5', gen.next(0)); // 5 { value: undefined, done: true }
其实你也可以这么理解, 赋值语句实际上是先执行右侧的表达式, 然后才进行赋值的, 在执行赋值语句右侧表达式时遇到了 yield
代码时就停止执行, 这时还未完成赋值操作, 当再次执行 next()
才会开始赋值, 这时赋值的值来自于 next()
的参数
5.5 yield*
yield*
后面可以跟着一个 可迭代对象
, yield*
会优先迭代完后面的 可迭代对象
, 再继续向下迭代, 如下代码所示: 遇到 yield*
关键词会先迭代完数组, 再继续往下
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i){
yield i;
yield* [1, 2, 3]; // yield* 后面跟着一个「可迭代对象」
yield i + 10;
}
const gen = generator(0)
gen.next() // { value: 0, done: false }
gen.next() // { value: 1, done: false }
gen.next() // { value: 2, done: false }
gen.next() // { value: 3, done: false }
gen.next() // { value: 10, done: false }
gen.next() // { value: undefined, done: true }
已知 生成器
返回的是 可迭代迭代器
, 那么我们这里就能够使用 yield*
关键词进行迭代, 相当于 迭代器
内部调用其他 迭代器
, 实现 迭代器
之间的嵌套使用
function* anotherGenerator(i) {
yield i + 1;
yield i + 2;
yield i + 3;
}
function* generator(i){
yield i;
yield* anotherGenerator(i); // yield* 后面跟「生成器」返回的 「可迭代迭代器」
yield i + 10;
}
const gen = generator(0)
for (const value of gen) {
console.log(value) // 0 1 2 3 10
}
5.6 return()、throw()
根据 迭代器协议
, 迭代器
具有可选属性 return()
throw()
, 这两个方法都是用于提前结束迭代, 可想而知 生成器
创建的 迭代器
也必然具有 return()
throw()
这两个方法
如下代码: gen1
和 gen2
是同一个迭代器生成的, gen1
进行正常迭代, gen2
迭代一次后, 调用了 return()
提前结束了迭代, 同时方法返回的 value
值等于传入的参数、done
值等于 true
function* generator(){
yield 1
yield 2
yield 3
}
const gen1 = generator()
gen1.next() // { value: 1, done: false }
gen1.next() // { value: 2, done: false }
gen1.next() // { value: 3, done: false }
gen1.next() // { value: undefined, done: true }
const gen2 = generator()
gen2.next() // { value: 1, done: false }
gen2.return('提前结束') // { value: '提前结束', done: true }
gen2.next() // { value: undefined, done: true }
如下代码: gen1
和 gen2
是同一个迭代器生成的, gen1
进行正常迭代, gen2
迭代一次后, 调用了 throw()
主动抛出一个错误并提前结束了迭代, 再次调用 next()
方法, 将会返回 { value: undefined, done: true }
function* generator(){
yield 1
yield 2
yield 3
}
const gen1 = generator()
gen1.next() // { value: 1, done: false }
gen1.next() // { value: 2, done: false }
gen1.next() // { value: 3, done: false }
gen1.next() // { value: undefined, done: true }
const gen2 = generator()
gen2.next() // { value: 1, done: false }
try {
gen2.throw(new Error('抛出错误')) // 调用者捕获到错误, 通过 throw 抛出一个错误, 并结束迭代
} catch {}
gen2.next() // { value: undefined, done: true }
补充: 需要注意的是当我们调用 return()
和 throw()
方法返回的 done
等于 true
, 表明迭代已经结束, 所以这两个方法返回的 value
值是无效的, 如下代码: for...of
中提前结束了循环, return()
方法返回的值没有任何作用, 因为迭代已经结束
function* generator(){
yield 1
yield 2
yield 3
}
const gen = generator()
for (const value of gen) {
console.log(value) // 1
if (value === 1) {
gen.return(10)
}
}
5.7 显式返回
生成器
中如果使用 return
提前结束函数的执行, 又是怎么的情况呢?
如下代码: 生成器
generator
中 return
出 10
, 迭代过程中遇到 return
语句会结束循环, next()
返回的 value
值等于 return
出来的值, done
等于 true
function* generator(){
yield 1
yield 2
return 10
yield 3
}
const gen = generator()
console.log(gen.next()) // { value: 1, done: false }
console.log(gen.next()) // { value: 2, done: false }
console.log(gen.next()) // { value: 10, done: true }
console.log(gen.next()) // { value: undefined, done: true }
同调用 return()
一样, 遇到 return
时, next()
返回的 done
为 true
, 表明迭代已经结束, 这时的 value
值是无效的, 如下代码所示: 使用 生成器
为 obj
定义了 Symbol.iterator
方法, 生成器中 retun
出 10
, 用 for...of
进行循环输出, 会发现只输出了 1
2
, 因为迭代过程中遇到 return
迭代已结束
function* generator(){
yield 1
yield 2
return 10
yield 3
}
for (const value of generator()) {
console.log(value) // 1 2
}
5.8 生成「异步迭代器」
之前我们都没讨论过 yield
关键词后面允许跟哪些内容, 实际上它后面允许跟 任何类型数据
、任何表达式
, yield
关键词后面如果是 表达式
, 关键字的值将是 表达式
的返回值
如此, 根据 异步迭代器协议
当 yield
关键词后面跟一个 Promise
实例, 那么该 生成器
将返回一个 异步迭代器
, 如下代码: 通过 生成器
为 异步可迭代对象
obj
定义了 Symbol.asyncIterator
方法, 生成器中 yield
跟着 Promise
实例, 最后使用 for await...of
对 异步可迭代对象
进行循环
const obj = {
*[Symbol.asyncIterator](){
let i = 0
while(i < 3) {
yield new Promise((resole) => {
setTimeout(() => resole(i), i * 1000)
})
i++
}
}
}
for await (const value of obj) {
const res = await value
console.log(res) // 立即打印 0, 1 秒后打印 1, 2 秒后打印 2
}
实际上 生成器
中不管 yield
关键词后面是不是 Promise
返回的 迭代器
都同时实现了 Symbol.iterator
和 Symbol.asyncIterator
方法, 所以它既是 可迭代迭代器
又是 异步可迭代迭代器
, 也能够同时被 for...of
和 for await...of
迭代, 如下代码:
function* generator() {
yield 1
yield 2
yield 3
}
const gen = generator(10)
for (const value of gen) {
console.log(value) // 1 2 3
}
for await (const value of gen) {
console.log(await value()) // 1 2 3
}
六、生成器妙用
6.1 惰性求值
生成器可以使用 yield
暂停函数执行并返回一个值, 下次调用时会从上次暂停的地方继续执行; 因此, 可以在需要时造计算、生成值, 而不是一次性生成所有值, 如此可以大大减少内存消耗和提高性能, 尤其是对于无限序列的情况, 例如斐波那契数列, 如下代码: 是一个生成器, 用于生成前 n
个斐波那契数:
function* fibonacci(n) {
let a = 0, b = 1;
for (let i = 0; i < n; i++) {
yield a;
[a, b] = [b, a + b];
}
}
for (let n of fibonacci(10)) {
console.log(n);
}
6.2 异步编程
生成器可以与 Promise
结合使用来编写异步代码, 从而使异步代码更易于编写和阅读, 使用生成器编写的异步代码可以更像同步代码, 并且可以使用 try/catch
语句来捕获错误:
function* asyncOperation(base) {
const step1 = yield new Promise(resolve => setTimeout(() => {
console.log('执行第一个步骤=>')
resolve(base + 1)
}, 1000));
const step2 = yield new Promise(resolve => setTimeout(() => {
console.log('执行第二个步骤=>')
resolve(step1 * 2)
}, 1000));
yield new Promise(resolve => setTimeout(() => {
console.log('执行第三个步骤=>')
resolve(step2 * 10)
}, 1000));
}
const gen = asyncOperation(10);
try {
// 1. 执行第一个步骤
gen.next().value.then(step1 => {
console.log(step1)
// 如果 step1 为空, 结束迭代
if (!step1) {
gen.return()
return
}
// 2. 执行第二个步骤
gen.next(step1).value.then(step2 => {
console.log(step2)
// 3. 执行第三个步骤
gen.next(step1).value.then(res => {
console.log(res)
})
});
})
} catch {
// 捕获到错误, 结束迭代
gen.throw(new Error('报错了'))
}
6.3 简化「迭代器」的实现
生成器
可以大大简化 迭代器
的实现, 因为 生成器
提供了内置的 迭代器
实现, 在 生成器
中我们只需要通过 yield
关键词定义每次迭代要生成的值即可:
const obj = {
*[Symbol.iterator](){
yield 1
yield 2
yield 3
}
}
for (const value of obj) {
console.log(value) // 1 2 3
}
6.4 用于数据处理
使用生成器可以实现数据处理, 例如将大型数据集分成小块进行处理, 或者从迭代器中获取一部分数据进行处理, 而不需要将整个数据集都加载到内存中, 下面是一个示例, 用于从一个大型数组中获取特定数量的元素并进行处理:
function* processArray(array, chunkSize) {
let i = 0;
while (i < array.length) {
yield array.slice(i, i + chunkSize);
i += chunkSize;
}
}
const array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (let chunk of processArray(array, 3)) {
console.log(`Processing chunk: ${chunk}`); // 处理每个块
}
6.5 实现协程
协程是一种特殊的函数, 它可以在执行过程中暂停并保存当前状态, 等待下一次执行时恢复状态, 并在不同的协程之间进行切换
在 JS
中, 可以使用 生成器函数
和 yield
关键字实现协程, 具体来说,协程函数
可以使用 yield
关键字暂停执行, 并返回一个值给调用方。调用方可以通过 next()
方法向 协程函数
发送数据, 并在 协程函数
中使用 yield
关键字接收数据, 协程函数
可以保存当前的状态, 并在下一次执行时恢复状态, 从而实现在不同的协程之间进行切换, 如下代码: 展示了使用 yield
关键字实现协程的过程
function* coroutine() {
let value = yield;
console.log(`Received value: ${value}`);
value = yield value * 2;
console.log(`Received value: ${value}`);
value = yield value * 3;
console.log(`Received value: ${value}`);
}
let gen = coroutine();
gen.next();
gen.next(1); // Received value: 1
gen.next(2); // Received value: 2, return value: 4
gen.next(3); // Received value: 3, return value: 9
七、总结
-
迭代器
是一个实现了迭代器协议
的对象, 该对象主要定义了一个next()
方法, 方法返回一个value
和done
,value
表示当前迭代的值,done
则表示当前迭代是否结束 -
异步迭代器
是一个实现了异步迭代器协议
的对象, 它和迭代器
的约定基本一致, 只是next()
方法返回的value
值规定是一个Promise
实例 -
可迭代对象
是一个实现了,可迭代协议
的一个对象, 该对象定义了Symbol.iterator
方法, 该方法返回一个迭代器
, 该对象能够被for...of
进行迭代 -
异步可迭代对象
是一个实现了,异步可迭代协议
的一个对象, 该对象定义了Symbol.asyncIterator
方法, 该方法返回一个迭代器
, 该对象能够被for await...of
进行迭代 -
生成器
是一个函数, 该函数返回一个迭代器
,生成器
可以方便我们快速创建一个迭代器
, 提供一些非常强大和灵活的功能 -
生成器
返回的迭代器
, 实现了Symbol.iterator
和Symbol.asyncIterator
方法, 所以它也是可迭代迭代器
、异步可迭代迭代器
, 能够被for...of
和for await...of
循环 x