Iterator-Generator
Iterator(迭代器)
查看
迭代器:使用户在容器对象(如数组)上遍访的对象。即迭代器是帮助我们对某个数据结构进行遍历的对象。
在JS中,迭代器是一个具体的对象,我们可以手动实现,但这个对象需要符合迭代器协议(iterator protocol)
-
迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式。
-
在JS中这个标准就是一个特定的next方法。
next方法有如下要求:
-
一个无参数或着一个参数的函数,返回一个应当拥有以下两个属性的对象:
- done(boolean)
- 如果迭代器可以产生序列中的下一个值,则为false。
- 如果迭代器已将序列迭代完毕,则为true。这种情况下,value是可选的,如果它依然存在,即为迭代结束后的默认返回值。
- value
- 迭代器返回的任何JavaScript值,done为true时可省略。
- done(boolean)
实现迭代器,以获取对象的键(包含Symbol)为例:
1 | function createObjIterator(obj) { |
可迭代对象
查看
可迭代对象和迭代器是两个不同的概念,前面我们可以通过迭代器对对象进行遍历操作,但需遍历对象和迭代器时分离开的,如果我们把迭代器和对象结合在一起,就叫做可迭代对象。
但将迭代器和可迭代对象结合起来成为可迭代对象,可是有一定规范的:
-
这个对象要实现迭代器协议(iterator protocol)。
-
这个对象必须实现
@@iterator
方法,在代码层面是对象要实现[Symbol.iterator]
属性。
可迭代对象是可以通过for of 循环进行遍历操作。
1 | const obj = { |
可迭代对象的使用场景:
-
JavaScript中语法:for …of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment)。
-
创建一些对象时:
new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable])
。 -
一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable)。
补充:
迭代器中其实还可以做一个操作,就是监听容器对象的中断操作:
-
比如遍历的过程中通过break、return、throw中断了循环操作。
如果我们想实现这个操作,就需要在迭代器对象中加入return方法。
1 | const obj = { |
Generator(生成器)
查看
生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。
平时我们写的函数终止条件要么是return,要么是发生了异常,生成器中又提供了yield关键字来控制函数的执行。
生成器函数也是一个函数,但与普通的函数有一些区别:
-
生成器函数需要在function后加上一个
*
-
生成器函数可以通过yield关键字控制函数的执行流程。
-
生成器函数的返回值是一个Generator对象。
- 生成器事实上是一种特殊的迭代器。
- Instead, they return a special type of iterator, called a Generator.
生成器实例代码:
1 | function* foo(y1) { |
生成器函数中的代码并不会默认执行,但可以通过其返回的生成器调用next的方法再根据yield关键字控制代码的执行,一次next的调用运行到一个yield关键字的位置,并返回一个对象,对象为{value: yield关键字后面的值,done: false},当next的调用返回的done为true时,表示函数执行完毕,此时返回的对象为{value: undefined, done: true}。也可以通过next方法向构造器函数中传值,通过yield关键字获取到传进来的值,但第一次next的传值调用是无法被接收到的。
对于生成器,我们可以直接调用其return和throw方法来控制函数的提前结束和抛出异常。
1 | function* foo(y1) { |
以上代码可以看出,生成器函数的调用会在生成器的return方法调用时提前结束,即使后面还有代码没执行完。return方法还会返回一个对象,对象为{value: return方法传进来的值,done: true},后面即使在调用next方法,结果也都是{value: undefined,done: true}了。
1 | function* foo(y1) { |
以上代码可以看出,生成器函数的调用会在生成器的throw方法调用时提前结束,并抛出异常,即使后面还有代码没执行完。throw方法还会返回一个对象,对象为{value: undefined,done: true},后面即使在调用next方法,结果也都是{value: undefined,done: true}了。
既然生成器是一种特殊的迭代器,那么我们可以用生成器替代迭代器,那上文的代码为例,用生成器重写后为:
1 | function* createObjIterator(obj) { |
用生成器实现可迭代对象的@@iterator
方法,只需要在[Symbol.iterator](){}
开头加一个*
,那么它就是一个生成器方法了。
1 | const obj = { |
上述我们实现迭代器或生成器内部获取元素的时候都是用的for of 循环,for of可以用于可迭代对象,如果内部循环的是一个可迭代对象的话,我们可以将操作简写以下。
1 | const obj = { |
Generator解决异步回调地狱问题
查看
场景:当我们发送一个异步请求,需要根据该请求结果发送另一个请求,另一个请求的结果时其他请求所必需的,依次类推,给出回调地狱的代码,尝试对其优化。
1 | function getData(){ |
我们可以通过返回Promise对其进行优化
1 | function getData(){ |
以上代码看起来好多了,但阅读性其实还是比较差的,接下来我们用生成器进行优化。
1 | function* getData(){ |
上述封装请求的生成器函数看起来挺清爽的,但是调用起来就麻烦了。
1 | const generator = getData() |
但是这样又变成了上述用Promise解决时的问题,但好在生成器的每次next调用都会返回一个对象,对象中有一个属性done可以判断生成器函数中的代码是否执行完毕,所以我们可以对其进行封装优化。
封装自动执行generator函数
1 | function* getData() { |