
JavaScript, Iterator, Generator
关于JS中Iterator和Generator的梳理
对JS中迭代器和生成器的一次
72次点击15分钟阅读
迭代器和 生成器
- 学习
rust
时,发现rust
中有很多和js
相似的概念(大部分语言都相通的语法特性),比如iterator
,rust和js底层都是用Gererator,yield
来实现await
语法的,通过yield
来实现协程,rust
中的Future
对应js
的promise
等 - 实现迭代器其实就是根据语言提供的迭代器接口,实现一些符合要求的方法
- 集合对象 -> 迭代器 ->遍历 :
- 迭代器到底是什么?
- 迭代器就是一个对象,在集合对象遍历时,运行时会自动调用迭代器提供的
next
方法来获取下一个要迭代的对象,异步迭代器同理
- 迭代器就是一个对象,在集合对象遍历时,运行时会自动调用迭代器提供的
yield
关键词有什么作用yield
语法只有在Generate
函数function*
中才有作用,用于暂停这个函数的执行,要想继续执行,需要调用nex
t方法-
yield
其实是有返回值的,这个返回值通常是由生成器函数调用的next
方法里的参数决定的
JS的迭代协议
- 迭代器确实是一种协议,而不是内置的实现或者语法,相当于是一种JS给我们提供的约定,按照约定实现指定的方法,就可以实现 对象的可迭代
(同步)迭代协议
迭代协议
分为两部分:可迭代协议(iterable protocol)
和迭代器协议(iterator protocol)
,这两个又是环环相扣的
1. 可迭代协议(iterable protocol)实现- 怎么成为一个可迭代的对象:?
- 要成为可迭代对象,对象必须实现
@@iterator
方法,对象或者原型链上必须有一个名为@@iterator
的属性,以及至少实现一个符合迭代器要求的next
方法 可通过常量Symbol.iterator访问该属性,
- 要成为可迭代对象,对象必须实现
-
[Symbol.iterator]
方法的三个特征- 这个函数没有参数
- 该方法需要返回一个符合
迭代器协议(iterable protocol)
的对象(比如返回一个包含next
方法的匿名对象,或者this
) - 该函数可以是普通函数,也可以是生成器(Generator)函数(生成器函数内部,使用
yield
提供entry
)
- 当迭代一个可迭代对象时发生了什么?
- 当一个对象需要被迭代的时候(比如被置入一个
for...of
循环时),首先,会不带参数调用它的@@iterator
方法,然后使用此方法返回的迭代器获得要迭代的值。
- 当一个对象需要被迭代的时候(比如被置入一个
迭代器协议(iterator protocol)实现
- 迭代器协议定义了产生一系列值的标准方式
- 怎么实现 ?
- 只有至少实现了一个拥有以下语义(semantic)的
next
方法,一个对象才能成为迭代器: next
函数需要为 无参数或者接受一个参数的函数,并返回符合IteratorResult
接口的对象
IteratorResult
接口其实就是一个包含done
,value
属性的对象,这两个属性都是可选的
- 只有至少实现了一个拥有以下语义(semantic)的
- 以下是MDN对 实现`迭代器协议(iterator protocol)`三个方法的描述
next
无参数或者接受一个参数的函数,并返回符合IteratorResult
接口的对象(见下文)。如果在使用迭代器内置的语言特征(例如for...of
)时,得到一个非对象返回值(例如false
或undefined
),将会抛出TypeError("iterator.next() returned a non-object value")
。IteratorResult
接口的对象是什么样的? 所有迭代器协议的方法(next()、return() 和 throw()
)都应返回实现IteratorResult
接口的对象。它必须有以下属性:done
可选 如果迭代器能够生成序列中的下一个值,则返回false
布尔值。(这等价于没有指定done
这个属性。) 如果迭代器已将序列迭代完毕,则为true
。这种情况下,value
是可选的,如果它依然存在,即为迭代结束之后的默认返回值。value
可选 迭代器返回的任何JavaScript
值。done
为true
时可省略。- 实际上,两者都不是严格要求的;如果返回没有任何属性的对象,则实际上等价于
{ done: false, value: undefined }
。 如果一个迭代器返回一个done: true
的结果,则对任何 next() 的后续调用也返回done: true
,尽管这在语言层面不是强制的。next
方法可以接受一个值,该值将提供给方法体。任何内置的语言特征都将不会传递任何值。传递给生成器next
方法的值将成为相应yield
表达式的值。
- 可选地,迭代器也实现了
return(value)
和throw(exception
) 方法,这些方法在调用时告诉迭代器,调用者已经完成迭代,并且可以执行任何必要的清理(例如关闭数据库连接)。 return(value)
可选 无参数或者接受一个参数的函数,并返回符合IteratorResul
t 接口的对象,其value
通常等价于传递的value
,并且done
等于true
。调用这个方法表明迭代器的调用者不打算调用更多的next
,并且可以进行清理工作。throw(exception)
可选 无参数或者接受一个参数的函数,并返回符合IteratorResult
接口的对象,通常done
等于true
。调用这个方法表明迭代器的调用者监测到错误的状况,并且exception
通常是一个Error
实例。
异步迭代协议
- 同样分为
异步可迭代协议(async iterable protocol)
和异步迭代器协议(async iterator protocol)
- 注意, 异步迭代器由
yield
和生成器函数来实现(awai
t语法本身也是yield
的语法糖) - 对象怎么实现
异步可迭代协议(async iterable protocol)
?- 实现
[Symbol.asyncIterator]
方法和符合异步迭代器要求的next
方法 - 那要如何实现一个
[Symbol.asyncIterator
]方法呢?- 返回符合
Iterable Result
接口的实例对象的无参数函数,并且符合异步迭代器协议(async iterator protocol)
。
- 返回符合
- 那如何符合异步迭代器协议呢?
- 对象需要至少实现一个符合要求的
nex
t方法
- 对象需要至少实现一个符合要求的
- 当对象实现以下方法时,这个对象就符合了`
异步迭代器协议(async iterator protocol)
next
无参数或者接受一个参数的函数,并返回promise
。promise
兑现为一个对象,该对象符合IteratorResult
接口,并且这些属性与同步迭代器有着相同的语义。return(value
) 可选 无参数或者接受一个参数的函数,并返回promise
。promise
兑现为一个对象,该对象符合IteratorResul
t 接口,并且这些属性与同步迭代器有着相同的语义。throw(exception
) 可选 无参数或者接受一个参数的函数,并返回promise
。promise
兑现为一个对象,该对象符合IteratorResult
接口,并且这些属性与同步迭代器有着相同的语义。
- 实现
生成器(Generator)
- 生成器函数什么?
- 生成器是
ECMAScript 6
新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的能力。这种新能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。
- 生成器是
- 生成器函数怎么创建
- 生成器的形式是一个函数,函数名称前面加一个星号(
*
)表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。(除了箭头,其他生成的方法都可以,包括Class里的静态函数)
- 生成器的形式是一个函数,函数名称前面加一个星号(
- 调用生成器函数时发生了了什么?
- 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与迭代器相似,生成器对象也实现了
Iterato
r 接口,因此具有next
方法。调用这个方法会让生成器开始或恢复执行。
- 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与迭代器相似,生成器对象也实现了
- 生成器对象如何生成,有什么特性?
- 生成器对象本身就是一个可迭代对象,可通过
for of
等来遍历,就不用显式的调用next
方法了 - 初次调用生成器函数时生成的为生成器对象
- 生成器对象本身就是一个可迭代对象,可通过
- 初次调用并不会执行函数体内的任何一行代码,返回了一个生成器,只有在初次调用next后才会执行代码
- 生成器对象也实现了
Iterato
r接口,它们默认的迭代器是自引用的,调用这个迭代器方法返回的还是本身(当前的生成器实例)
- 生成器函数的执行 暂停和恢复
- 生成器函数代码遇到
yield
会暂停执行,保留作用域,使用`生成器函数的实例`调用next
方法,继续执行 - 同一个生成器函数生成的多个
Generator
实例,是独立的,调用next
方法时是不会互相影响的 yield
的关键字必须出现在生成器函数的定义中,即便是在生成器函数中嵌套定义一个普通的函数里面使用yield
也会报错yield*
语法,可以使用星号增强yield
的行为,让它能够迭代一个可迭代对象(比如使用yield
遍历一个数组时),从而一次产出一个值
- 生成器函数代码遇到
Generator
实例的三个方法- 显示调用
next
方法如g.next()
或者g.next('传给yield要返回的参数')
,初始化执行以及恢复生成器函数内部因为yield
或者yield*
而暂停的函数 - 显示调用
g.return()
可提前终止迭代 - 显示调用
g.throw()
用于报错,如果生成器函数内部没有处理,g就会提前终止
- 显示调用
问题
1. for...of遍历是怎么知道什么时候该停止遍历的
- 其实
yield
的作用可以用来简化迭代器的实现,相当于自动实现了next
方法 - 对于使用
yield
的生成器函数 ,这是因为生成器实例实现了迭代器协议。当生成器函数中没有更多的yield
语句时,生成器实例的next
方法会返回一个对象,其done
属性为tru
e,表示迭代已经完成。for...of
循环会检查这个done
属性,当它为true
时,循环就会停止。
- 对于手动实现
next
方法的迭代对象,需要手动在next
函数的返回中返回一个对象,在合适的条件下使得属性done
为true
,一定要返回,不然使用for...of
遍历的时候就死循环了