迭代器可迭代对象

什么是迭代器?

迭代器(iterator),使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。

  • 其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
  • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;

从迭代器的定义我们可以看出来,迭代器是帮助我们对某个数据结构进行遍历的对象。
在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol):

  • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式;
  • 在JavaScript中这个标准就是一个特定的next方法;

next方法有如下的要求:

  • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
  • done(boolean)
    • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
    • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
  • value
    • 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

      迭代器的代码练习

      image.png
      image.png

原生的迭代器对象

可迭代对象

但是上面的代码整体来说看起来是有点奇怪的:

  • 我们获取一个数组的时候,需要自己创建一个index变量,再创建一个所谓的迭代器对象;
  • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象;

什么又是可迭代对象呢?

  • 它和迭代器是不同的概念;
  • 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
  • 这个对象的要求是必须实现 @@iterator 方法,在代码中我们使用 Symbol.iterator 访问该属性;

当然我们要问一个问题,我们转成这样的一个东西有什么好处呢?

  • 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作;
  • 比如 for…of 操作时,其实就会调用它的 @@iterator 方法;

    可迭代对象的代码

    image.png

    原生迭代器对象

    事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

  • String、Array、Map、Set、arguments对象、NodeList集合;

image.png
image.png

可迭代对象的应用

那么这些东西可以被用在哪里呢?

  • 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);

image.png
image.png

自定义类的迭代器

自定义类的迭代

在前面我们看到Array、Set、String、Map等类创建出来的对象都是可迭代对象:

  • 在面向对象开发中,我们可以通过class定义一个自己的类,这个类可以创建很多的对象:
  • 如果我们也希望自己的类创建出来的对象默认是可迭代的,那么在设计类的时候我们就可以添加上 @@iterator 方法;

案例:创建一个classroom的类

  • 教室中有自己的位置、名称、当前教室的学生;
  • 这个教室可以进来新学生(push);
  • 创建的教室对象是可迭代对象;

自定义类的迭代实现

image.png
image.png

迭代器的中断

迭代器在某些情况下会在没有完全迭代的情况下中断:

  • 比如遍历的过程中通过break、return、throw中断了循环操作;
  • 比如在解构的时候,没有解构所有的值;

那么这个时候我们想要监听中断的话,可以添加return方法:
image.png
image.png

生成器理解和作用

什么是生成器?

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等。

  • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

生成器函数也是一个函数,但是和普通的函数有一些区别:

  • 首先,生成器函数需要在function的后面加一个符号:*
  • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:
  • 最后,生成器函数的返回值是一个Generator(生成器):

    • 生成器事实上是一种特殊的迭代器;
    • MDN:Instead, they return a special type of iterator, called a Generato

      生成器函数执行

      我们发现下面的生成器函数foo的执行体压根没有执行,它只是返回了一个生成器对象。
  • 那么我们如何可以让它执行函数中的东西呢?调用next即可;

  • 我们之前学习迭代器时,知道迭代器的next是会有返回值的;
  • 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果;

image.png
image.png

生成器传递参数 – next函数

函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?

  • 答案是可以的;
  • 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值;
  • 注意:也就是说我们是为本次的函数代码块执行提供了一个值;

image.png

生成器提前结束 – return函数

还有一个可以给生成器函数传递参数的方法是通过return函数:

  • return传值后这个生成器函数就会结束,之后调用next不会继续生成值了;

image.png

生成器抛出异常 – throw函数

除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常:

  • 抛出异常后我们可以在生成器函数中捕获异常;
  • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行;

image.png
image.png

生成器替代迭代器

我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
image.png
image.png
事实上我们还可以使用yield*来生产一个可迭代对象:

  • 这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值;

image.png

自定义生成器方案

自定义类迭代 – 生成器实现

在之前的自定义类迭代中,我们也可以换成生成器:
image.png

对生成器的操作

既然生成器是一个迭代器,那么我们可以对其进行如下的操作:
image.png

异步处理方案解析

学完了我们前面的Promise、生成器等,我们目前来看一下异步代码的最终处理方案。
案例需求:

  • 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;
  • 第二次的请求url依赖于第一次的结果;
  • 第三次的请求url依赖于第二次的结果;
  • 依次类推;

image.png
image.png

Generator方案

◼ 但是上面的代码其实看起来也是阅读性比较差的,有没有办法可以继续来对上面的代码进行优化呢?
image.png
image.png

自动执行generator函数

目前我们的写法有两个问题:

  • 第一,我们不能确定到底需要调用几层的Promise关系;
  • 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?

所以,我们可以封装一个工具函数execGenerator自动执行生成器函数:
image.png

Day11 作业布置

三. 什么是迭代器?什么是可迭代对象?

  • 迭代器

    • 迭代器是帮助我们对某个数据结构进行遍历的对象
    • 迭代器也是一个具体的对象,这个对象需要符合迭代器协议
      • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式
      • JavaScript中这个标准就是一个特定的next方法
    • next方法的要求
      • 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
      • doneboolean
        • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done这个属性。)
        • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
      • value
        • 迭代器返回的任何 JavaScript值。donetrue时可省略
          1. // 封装一个为数组创建迭代器的函数
          2. function createArrayIterator(arr) {
          3. let index = 0
          4. return {
          5. next: function() {
          6. if (index < arr.length) {
          7. return { done: false, value: arr[index++] }
          8. } else {
          9. return { done: true }
          10. }
          11. }
          12. }
          13. }
  • 可迭代对象

    • 和迭代器不是一个概念
      • 当一个对象实现了iterable protocol协议时,它就是一个可迭代对象;
      • 这个对象的要求是必须实现 @@iterator方法,在代码中我们使用 Symbol.iterator访问该属性
    • 转成这样的好处
      • 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作
      • 比如 for...of 操作时,其实就会调用它的@@iterator 方法
    • 实现可迭代协议的原生对象
      • StringArrayMapSetarguments对象、NodeList集合…
    • 可迭代对象的应用
      • 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中断了循环操作
      • 比如在解构的时候,没有解构所有的值
    • 自定义类的迭代实现
      1. class Person {
      2. constructor(name, age, height, friends) {
      3. this.name = name
      4. ...
      5. }
      6. // 实例方法
      7. running() {}
      8. / [Symbol.iterator]() {
      9. let index = 0
      10. const iterator = {
      11. next: () => {
      12. if (index < this.friends.length) {
      13. return { done: false, value: this.friends[index++] }
      14. } else {
      15. return { done: true }
      16. }
      17. }
      18. }
      19. return iterator
      20. }
      21. }

四. 什么是生成器?生成器和迭代器有什么关系?

  • 生成器 (ES6新增)

    • 生成器函数也是一个函数,但是和普通的函数有一些区别
      • 首先,生成器函数需要在function的后面加一个符号:*
      • 其次,生成器函数可以通过yield关键字来控制函数的执行流程:
      • 最后,生成器函数的返回值是一个(生成器)
        • 生成器事实上是一种特殊的迭代器
    • 生成器函数

      1. 生成器函数:
      2. 1.function后面会跟上符号: *
      3. 2.代码的执行可以被yield控制
      4. 3.生成器函数默认在执行时, 返回一个生成器对象
      5. * 要想执行函数内部的代码, 需要生成器对象, 调用它的next操作
      6. * 当遇到yield时, 就会中断执行
    • 生成器传递参数 — next函数

      • 调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值
      • 也就是说我们是为本次的函数代码块执行提供了一个值
    • 生成器提前结束
      • return传值后这个生成器函数就会结束,之后调用next不会继续生成值
    • 生成器抛出异常 — throw函数
      • 抛出异常后我们可以在生成器函数中捕获异常
      • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行
    • 生成器替代迭代器

      • 使用yield来生产一个可迭代对象
      • 这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值
        1. function* createArrayIterator(arr) {
        2. yield* arr
        3. }
    • 自定义类迭代 — 生成器实现

      1. // 以Person为例 添加到实例方法
      2. *[Symbol.iterator]() {
      3. yield* this.friends
      4. }

一. 完成课堂所有的代码

二. 异步函数和普通函数的区别

  • 异步函数
    • 使用async关键字声明的函数 ```javascript // async function foo() {}

// const bar = async function() {}

// const baz = async () => {}

// class Person { // async running() {} // }

  1. - 异步函数的执行流程
  2. - 异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行
  3. - 返回值和普通函数的区别
  4. - 情况一:异步函数也可以有返回值,但是异步函数的返回值相当于被包裹到`Promise.resolve`
  5. - 情况二:如果我们的异步函数的返回值是`Promise`,状态由会由`Promise`决定;
  6. - 情况三:如果我们的异步函数的返回值是一个对象并且实现了`thenable`,那么会由对象的方`then`法来决定
  7. - 如果在`async`函数中抛出异常
  8. - 并不会报错, 而是作为`Promise``reject`来传递
  9. <a name="f244f913"></a>
  10. ## 三. 说说线程和进程的区别以及关系
  11. - **进程**
  12. - `cpu`分配资源的最小单位;(是能拥有资源和独立运行的最小单位)
  13. - 计算机已经运行的程序,是操作系统管理程序的一种方式 (**官方说法**)
  14. - 可以认为启动一个应用程序,就会默认启动一个进程(也可能是多个进程)**(个人解释)**
  15. - 也可以说进程是线程的容器
  16. - **线程**
  17. - `cpu`调度的最小单位;(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
  18. - 操作系统能够运行运算调度的最小单位,通常情况下它被包含在进程中 **(官方说法)**
  19. - 每一个进程中,都会启动至少一个线程用来执行程序中的代码,这个线程被称之为主线程
  20. - **操作系统的工作方式**
  21. - 如何做到同时让多个进程同时工作?
  22. - 因为`CPU`的运算速度非常快, 可以快速的在多个进程之间迅速的切换
  23. - 当进程中的线程获取到世间片时, 就可以快速执行我们编写的代码
  24. - 由于`CPU`执行速度过于变态, 对于用户来说是感受不到这种快速切换的
  25. - **浏览器中**`**JavaScript**`**线程**
  26. - `**JavaScript**`**为什么是单线程的**
  27. ```javascript
  28. // 这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变
  • 进程容器是浏览器或者Node
    • 浏览器是多进程的?
      1. // 放在浏览器中,每打开一个tab页面,其实就是新开了一个进程,在这个进程中,还有ui渲染线程,js引擎线程,http请求线程等。所以,浏览器是一个多进程的。为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM.所以,这个新标准并没有改变JavaScript单线程的本质。
  • 目前多数浏览器都是多进程的, 打开一个tab页面就会开启一个新的进程, 防止因为一个页面的卡死导致浏览器的强制退出
  • 每个进程中又有很多的线程,其中包括执行JavaScript代码的线程
    • 线程中JavaScript代码的执行
  • 它是在一个单独的线程中执行的, 意味着JavaScript代码在同一时刻只能做一件事
  • 非常耗时, 意味着当前线程会被阻塞
    • 所以耗时的操作并不是在JavaScript线程中执行的
  • 浏览器的每个进程是多线程的,那么其他线程可以来完成这个耗时的操作
  • 比如网络请求、定时器,只需要在特性的时候执行应该有的回调即可

四. 说说你对事件队列、微任务、宏任务的理解

  • 事件队列
    • 事件队列是一种数据结构,可以存放要执行的任务。它符合队列先进先出”的特点
  • 宏/微任务
    • 首先它们都是异步任务
    • 宏任务列队
      • 用来保存待执行的宏任务(回调)
      • 如: pajax、setTimeout、setInterval、DOM监听、UI Rendering等 会被加入到宏列队
    • 微任务列队
      • 用来保存待执行的微任务(回调)
      • 如: Promisethen回调、 Mutation Observer API、queueMicrotask()等 会被加入到微列队
    • JS执行时会区别这两个队列
      • JS引擎首先必须先执行所有的初始化同步任务代码
      • 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行,
      • 也就是优先级比宏任务高,且与微任务所处的代码位置无关
      • 也就是宏任务执行之前,必须保证微任务队列是空的;
      • 如果不为空,那么就优先执行微任务队列中的任务(回调)

五. 掌握微任务、宏任务相关的面试题