迭代器的介绍

迭代器是帮助我们对某个数据结构进行遍历的对象。

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

next方法有如下的要求:

一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
done:该值为ture和fasle,有值得情况下会一直显示fasle,当没有值得时候会显示为true。
value:迭代器返回的任何 JavaScript 值。当done为ture的时候可以省略。

实现一个迭代器

// 数组
const names = ["abc", "cba", "nba"]

// 创建一个迭代器对象来访问数组
let index = 0

const namesIterator = {
  next: function() {
    if (index < names.length) {
      return { done: false, value: names[index++] }
    } else {
      return { done: true, value: undefined }
    }
  }
}

console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next()) // { done: false, value: "nba" }
console.log(namesIterator.next()) // { done: true, value: undefined }
console.log(namesIterator.next()) // { done: true, value: undefined }
console.log(namesIterator.next()) // { done: true, value: undefined }
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())


可迭代对象

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

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

什么又是可迭代对象呢?

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

下面就来实现一个可迭代对象

实现的时候注意this的指向。



// 创建一个迭代器对象来访问数组
const iterableObj = {
  names: {baskball:"火箭",baskball:"勇士",baskball:"骑士"},
// 他需要具有[Symbol.iterator]方法,并且这个方法返回一个迭代器
  [Symbol.iterator]: function() {
    let index = 0
    console.log(this.names.baskball);
    return {
      next: () => {
        if (index < Object.values(this.names.baskball).length) {
          index ++
          return { done: false, value: this.names.baskball }
        } else {
          return { done: true, value: undefined }
        }
      }
    }
  }
}
for (const item of iterableObj) {
  console.log(item)
}


事实上我们平常创建许多原生对象都已经实现了可迭代协议
比如String、Array、Map、Set、arguments对象、NodeList集合
都可以使用:for ...of、展开语法(spread syntax)语法来使用它们

什么是生成器

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

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

  • 首先,生成器函数需要在function的后面加一个符号:*
  • 其次,生成器函数可以通过yield关键字来控制函数的执行流程
  • 最后,生成器函数的返回值是一个Generator(生成器):生成器事实上是一种特殊的迭代器
function* foo() {
  console.log("函数开始执行~")

  const value1 = 100
  console.log("第一段代码:", value1)
  yield

  const value2 = 200
  console.log("第二段代码:", value2)
  yield

  const value3 = 300
  console.log("第三段代码:", value3)
  yield

  console.log("函数执行结束~")
}

// 调用生成器函数时, 会给我们返回一个生成器对象
const generator = foo()
console.log(generator) //Object [Generator] {}


我们发现上面的生成器函数foo的执行体压根没有执行,它只是返回了一个生成器对象,那么我们该怎么执行呢?

只需要调用generator.next(),但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果。

生成器提前结束 – return函数

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

function* foo(num) {
  console.log("函数开始执行~")

  const value1 = 100 * num
  console.log("第一段代码:", value1)
  const n = yield value1

  const value2 = 200 * n
  console.log("第二段代码:", value2)
  const count = yield value2

  const value3 = 300 * count
  console.log("第三段代码:", value3)
  yield value3

  console.log("函数执行结束~")
  return "123"
}

const generator = foo(10)

console.log(generator.next())

// 第二段代码的执行, 使用了return
// 那么就意味着相当于在第一段代码的后面加上return, 就会提前终端生成器函数代码继续执行
console.log(generator.return(15))
console.log(generator.next())

生成器抛出异常 – throw函数

抛出异常后我们可以在生成器函数中捕获异常;

但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行

function* foo() {
  console.log("代码开始执行~")

  const value1 = 100
  try {
    yield value1
  } catch (error) {
    console.log("捕获到异常情况:", error)

    yield "abc"
  }

  console.log("第二段代码继续执行")
  const value2 = 200
  yield value2

  console.log("代码执行结束~")
}

const generator = foo()

const result = generator.next()
generator.throw("error message")


Q.E.D.