什么是迭代器(Iterator)?
当一个对象被认为是一个迭代器时,它实现了一个 next()
的方法并且拥有以下含义:对象的next
方法是一个无参函数,它返回一个对象,该对象拥有done
和value
两个属性:
done
: 布尔值- 如果迭代器已经经过了被迭代序列时为
true
。这时value
可能描述了该迭代器的返回值。 - 如果迭代器可以产生序列中的下一个值,则为
false
。这等效于连同done
属性也不指定。
- 如果迭代器已经经过了被迭代序列时为
value
:迭代器返回的任何JavaScript值。done
为true时可省略
手写一个简单迭代器
|
|
一些迭代器是转换自可迭代对象:
|
|
next()迭代
让我们来看一个数组,它是一个可迭代对象,可以生成一个迭代器来消费它的值:
|
|
每一次定位在[Symbol.iterator]
上的方法在值arr
上被调用时,它都将生成一个全新的迭代器。
|
|
基本类型的字符串值也默认地是可迭代对象
|
|
- 一个迭代器的
next(…)
方法能够可选地接收一个或多个参数 - 大多数内建的迭代器不可以
- Generator的迭代器可以
根据一般的惯例,包括所有的内建迭代器,在一个已经被耗尽的迭代器上调用next(…)
不是一个错误,而是简单地持续返回结果{ value: undefined, done: true }
。
可选的return(..)
return(..)
被定义为向一个迭代器发送一个信号,告知它消费者代码已经完成而且不会再从它那里抽取更多的值。这个信号可以用于通知生产者(应答next(..)
调用的迭代器)去实施一些可能的清理作业,比如释放/关闭网络,数据库,或者文件引用资源。
如果一个迭代器拥有return(..)
,而且发生了可以自动被解释为非正常或者提前终止消费迭代器的任何情况,return(..)
就将会被自动调用。你也可以手动调用return(..)
。
可选的throw(..)
throw(..)
被用于向一个迭代器发送一个异常/错误信号,与return(..)
隐含的完成信号相比,它可能会被迭代器用于不同的目的。它不一定像return(..)
一样暗示着迭代器的完全停止。
例如,在generator迭代器中,throw(..)
实际上会将一个被抛出的异常注射到generator暂停的执行环境中,这个异常可以用try..catch
捕获。一个未捕获的throw(..)
异常将会导致generator的迭代器异常中止。
注意
- 在迭代器接口上的可选方法 ——
return(..)
和throw(..)
—— 在大多数内建的迭代器上都没有被实现。但是,它们在generator的上下文环境中会用到。 - 根据一般的惯例,在
return(..)
或throw(..)
被调用之后,一个迭代器就不应该在产生任何结果了。
什么是可迭代对象(Iterable)?
满足可迭代协议的对象是可迭代对象。
可迭代协议: 对象的[Symbol.iterator]
值是一个无参函数,该函数返回一个迭代器。
在ES6中,所有的集合对象(Array
、 Set
与 Map
)以及String
、arguments
都是可迭代对象,它们都有默认的迭代器。
内建迭代器(iterator)也是可迭代对象(iterable)
|
|
自定义迭代器可以通过给予迭代器一个简单地返回它自身的[Symbol.iterator]
方法,就可以使它成为一个可迭代对象
|
|
可迭代对象可以在以下语句中使用:
for…of循环
|
|
理解for...of
循环
for...of
接受一个可迭代对象(Iterable),或者能被强制转换/包装成一个可迭代对象的值(如’abc’)。遍历时,for...of
会获取可迭代对象的[Symbol.iterator]()
,对该迭代器逐次调用next(),直到迭代器返回对象的done
属性为true
时,遍历结束,不对该value处理。
|
|
|
|
扩展运算符
|
|
解构赋值
|
|
yield*
|
|
什么是生成器(Generator)?
生成器对象是由一个生成器函数( generator function )返回的,并且它符合可迭代协议和迭代器协议。
生成器函数
生成器函数是能返回一个生成器的函数。生成器函数由放在function关键字之后的一个星号*
来表示,并能使用新的yield
关键字。
生成器函数简单地说就是可以暂停的函数
|
|
语法
|
|
执行一个Generator
虽然一个generator使用*
进行声明,但是你依然可以像一个普通函数那样执行它:
|
|
你依然可以传给它参数值,就像:
|
|
主要区别在于,执行一个generator,比如foo(5,10)
,并不实际运行generator中的代码。取而代之的是,它生成一个迭代器来控制generator执行它的代码。
yield
暂停
Generator还有一个你可以在它们内部使用的新关键字,用来表示暂停点:yield
。考虑如下代码:
|
|
输入和输出
yield
不只是暂停点,还可以在暂停generator时向外输出
|
|
yield
不只是暂停点,还可以在暂停generator时等待一个输入值, 这个值稍后被用于各个表达式环境中
|
|
坑:yield优先级问题
|
|
|
|
生成器对象
生成器对象既是迭代器,有是可迭代对象
|
|
在生成器中return
遍历器返回对象的done
值为true
时迭代器即结束,不对该value
处理。
|
|
done
值为true时迭代即结束,迭代不对该value处理。所以对这个迭代器遍历,不会对值2
处理。
|
|
|
|
|
|
在生成器中throw
|
|
小结:通过上面我们可以看出,next()
可以产生三种类型的值
- 对于可迭代序列中的一项a, 它返回
{ value: a, done: false}
- 对于可迭代序列的最后一项,明确是
return
返回的是b, 它返回{ value: b, done: true }
- 对于异常,它抛出这个异常。
生成器委托yield*
|
|
yield*
考虑可迭代对象的最后一个值
|
|
generator能扮演的角色
1. generator作为迭代器(数据生产者)
每一个yield
可以通过next()
返回一个值,这意味着generator可以通过循环或递归生产一系列的值,因为generator对象实现了Iterable接口。
generators同时实现了接口Iterable 和 Iterator,这意味着,generator函数返回的对象是一个迭代器也是一个可迭代的对象。
|
|
2. generator作为观察者(数据消费者)
yield
可以通过next()
接受一个值,这意味着generator变成了一个暂停执行的数据消费者直到通过next()
给generator传递了一个新值。
作为观察者,生成器暂停,直到它接收到输入。有三种输入,通过接口指定的方法传输:
next()
发送正常输入。return()
终止生成器。throw()
发出错误信号。
2.1next()
发送正常输入
|
|
2.2return()
终止generator
return()
在生成器里的yield
的位置执行return
|
|
组织终止
我们可以阻止return()
终止generator如果yield
是在finally
块内(或者在finally
中使用return
语句)
|
|
2.3throw()
一个错误
|
|
捕获一下
|
|
3. 协作程序(协同多任务)
考虑到generator是可以暂停的并且可以同时作为数据生产者和消费者,不会做太多的工作就可以把generator转变成协作程序(合作进行的多任务)
To be continued…
参考文档
[你不知道的JavaScript:异步与性能–第四章:Generator
本文结束,感谢阅读。
本文作者:melody0z
本文链接:https://melodyvoid.github.io/JavaScript/理解Iterable、Iterator、Generator.html
欢迎转载,转载请注明文本链接