迭代器与生成器

循环、迭代、递归区别

循环、迭代、递归均有“重复”的含义。

种类 定义
循环 最基础的概念,凡是重复执行某行代码,都称之为循环
迭代 数学中的迭代表示在多次循环中逐步接近结果;编程中的迭代表示按顺序访问线性结构中的每一项
递归 在函数内调用自身,将复杂情况逐步转变为基本情况

迭代与循环的区别:循环代码中参与运算的变量同时是保存结果的值,当前保存的值作为下一次循环的初始值。

迭代器模式

可迭代对象(Iterable)

可迭代对象(Iterable):数组、集合和具有类似数组行为的数据结构。它们包含的元素是有限的,而且都具有无歧义的遍历顺序。

迭代器(Iterator):迭代器(Iterator)可以消费(Consume)可迭代对象(Iterable)。
1.迭代器是按需创建的一次性对象,迭代器API使用next()方法在可迭代对象中遍历数据;每次成功调用next(),返回一个IteratorResult对象;如果不调用next(),则无法知道迭代器当前的位置。
2.每个迭代器都会关联一个可迭代对象。
3.迭代器无需了解与其相关联的可迭代对象的结构,只需要知道如何取得连续的值。

内置可迭代类型

  • 字符串:String
  • 数组:Array
  • 映射:Map
  • 集合:Set
  • arguments:传递给函数的参数的类数组对象。
  • NodeList等DOM集合类型

接收可迭代对象的原生语言

  • for...of:循环遍历
  • 数组解构:[...Array]
  • Array.from()
  • 创建集合:new Set(可迭代对象)
  • 创建映射:new Map(可迭代对象)
  • Promise.all()接收由期约组成的可迭代对象
  • Promise.reac()接收由期约组成的可迭代对象
  • yield* 操作符,在生成器中使用

可迭代协议

作用:允许 JavaScript 对象定义或定制它们的迭代行为。

要成为可迭代对象, 一个对象必须实现 @@iterator 方法。这意味着对象(或者它原型链上的某个对象)必须有一个键为 @@iterator 的属性,可通过常量 Symbol.iterator 访问该属性:

属性
[Sysbol.Iterator] 一个无参的函数,其返回值为一个符合迭代器协议的对象
迭代执行流程

当一个对象需要迭代的时候,首先,会不带参数调用它的@@iterator方法,然后使用此方法返回的迭代器获取迭代的值。

迭代器执行流程.png

@@iterator方法的调用将作为可迭代对象的方法进行调用,所以在此函数内部可以访问可迭代对象的属性,以决定在迭代过程中提供什么。

@@iterator方法可以普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数的内部,可以使用yield提供每个条目。

迭代器协议

迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个,所有的值被迭代完毕后,则会返回一个默认返回值。

迭代器的标准:只有实现一个拥有以下语义的next()方法,一个对象才能成为迭代器:

属性
next 一个无参函数,返回以下类型的对象:

done(boolean):迭代完毕为false,迭代未完毕为true
value:返回任何JavaScript值,done为true可以忽略

迭代器是按需创建的一次性对象,迭代器API使用next()方法在可迭代对象中遍历数据;每次成功调用next(),返回一个IteratorResult对象;如果不调用next(),则无法知道迭代器当前的位置。

迭代器的注意事项

[1]创建迭代器(Iterator):访问可迭代对象中的@@Iterator方法。let iterator = arr[Symbol.iterator]();
[2]访问可迭代对象中的值:调用next()方法,直至不产生新值。只要done的状态为true,表示已经访问完毕,后面再调用next()方法,返回值相同。
[3]不同迭代器之间没有关系。
[4]迭代器与可迭代对象进行绑定,如果可迭代对象修改了,迭代器也会反映相应的变化。
[5]next()方法必须返回一个对象,否则会抛出一个TypeError异常。

提前终止迭代器

可选的return()方法用于指定在迭代器提前关闭时执行的逻辑。提前终止迭代器可能的情况如下:

  • for-of循环通过break、continue、return、throw提前退出。
  • 结构操作并未消费所有值。

如果迭代器没有关闭,则还可以继续从上次离开的地方继续迭代。如数组的迭代器就是不能关闭的。

生成器

生成器:拥有在一个函数块内暂停和恢复代码执行的能力。该能力的体现:自定义迭代器和实现协程。

生成器基础

生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。只要定义函数的地方就可以定义生成器。

生成器函数实现了Iterable接口,因此可以用在任何消费可迭代对象的地方。

语法
function * 生成器函数() {}

class {
  * 生成器函数() {}
}

注意事项:

1、生成器函数不能当做构造器使用。
2、箭头函数不能用来定义生成器函数。
3、生成器函数返回一个生成器对象(Genertor)

Generator原型对象中常用的方法有:

方法 作用
Generator.prototype.next() 返回一个由yield表达式生成的值
Generator.prototype.return() 返给定的值并结束生成器
Generator.prototype.throw() 向生成器抛出一个错误
生成器函数执行流程:

1.调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行的状态。
2.调用生成器函数的next()方法,其内函数体开始执行直至遇到yeild的位置为止。【生成器函数只会在生成器对象执行next()方法的时候执行】

生成器执行流程.png

中断生成器:yield和*yield

yield

yield关键字用来暂停和恢复一个生成器函数,即生成器的能力体现。生成器函数在遇到yield关键字之前会正常执行,遇到此关键字后,执行会停止,函数作用域的状态也会保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。

语法[rv] = yield [expression]
expression:定义通过迭代器协议从生成器函数返回的值。如果省略,则返回undefined
rv:返回传递给生成器的next()方法的可选值,以恢复其执行。

作用
1、yield关键字使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。
2、yield作为中间参数使用。生成器函数返回一个生成器对象,第一次执行next()方法的时候,next()传递的参数不会被使用,因为这一次调用是为了开始执行生成器函数。之后调用next()方法的时候,传递的参数将会被使用。

返回值:yield关键字实际返回一个IteratorResult对象,有两个属性:value和done。value对yield表达式求值的结果,done表示生成器函数是否完成。

迭代器对象中的next()与生成器对象中的next()的区别

  • 迭代器对象中的next()方法不接收参数
  • 生成器对象中的next()方法可以接收参数

注意事项
[1] 多个生成器对象之间相互不干扰。
[2] yield关键字只能在生成器函数内部使用,在其他地方使用就会抛出异常。【内部嵌套的非生成器函数也不行】
[3] 生成器对象作为可迭代对象:生成器对象当做可迭代对象,使用更加方便。
[4] yield不仅可以作为函数的中间返回语句使用,还可以作为函数中间参数使用。

增强版的yield--->yield *

yield *能够迭代一个可迭代对象,从而一次产出一个值。

作用
1、迭代一个可迭代对象
2、递归操作

案例:

// 迭代一个可迭代对象
function * a() {
    yield * [1,2,3,4]
}
for (let value of a()) {
    console.log(value)
}

// 递归操作
function * b(n) {
    if (n > 0) {
      yield * b(n-1);
      yield n-1;
  }
}
for (let value of b(3)) {
  console.log(value)
}
区别 yield yield *
定义 让生成器停止和开始执行 增强yield行为,迭代可迭代对象
作用 暂停函数的执行
通过next()实现输入输出
递归操作

提前终止生成器

与迭代器类似,生成器也支持可关闭状态。生成器对象有return()和throw()方法进行终止生成器。
1、return() 强制生成器进行关闭状态,进入关闭方法后,无法恢复。
2、throw()暂停的时候,将一个提供的错误注入到生成器对象中。如果错误未处理,生成器关闭;否则生成器不会关闭。

生成器作为默认迭代器

因为生成器对象实现了Iterable接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器很适合作为默认迭代器。

class Foo {
    constructor() {
        this.values = [1,2,3,4]
    }
    * [Symbol.iterator]() {
        yield * this.values;
    }
}

const f = new Foo();
for (const x of f) {
    console.log(x)
}

迭代器 VS 生成器

区别 迭代器 生成器
来源 可迭代对象的Symbol.iterator方法的返回值 生成器函数的返回值
next()方法访问当前的值 返回{done: true/false, value: JavaScript类型/undefined} 返回{done: true/false, value: JavaScript类型/undefined}
return()终止 迭代器没有此函数,但是可以添加。添加后,返回格式与next()相同,但是不会真正终止 真正终止生成器
throw() 迭代器没有此函数 如果内部捕获,不会终止;否则终止
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容