文章目录

    • 1.生成器介绍
      • 1.1 什么是生成器
      • 1.2 生成器函数执行
      • 1.3 生成器传递参数
      • 1.4 生成器提前结束
      • 1.5 生成器抛出异常
    • 2.生成器应用
      • 2.1 生成器替代迭代器
      • 2.2 生成器实现自定义类迭代

1.生成器介绍

1.1 什么是生成器

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

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

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

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

生成器事实上是一种特殊的迭代器

1.2 生成器函数执行

我们在代码中演示一下:

  1. 生成器需要在function后面添加一个符号 *
// 定义了一个生成器函数
function* foo() {
  console.log("00001")
  console.log("00002")
  console.log("00003")
  console.log("00004")
  console.log("00005")
  console.log("00006")
}

foo()
  1. 生成器代码的执行可以被yield控制
// 定义了一个生成器函数
function* foo() {
  console.log("00001")
  console.log("00002")
  yield
  console.log("00003")
  console.log("00004")
  yield
  console.log("00005")
  console.log("00006")
}

foo()
  1. 生成器函数默认在执行时, 返回的也是一个生成器对象, 我们发现上面代码中, 调用函数时函数内部的代码并没有执行

    如果想要执行函数内部的代码, 需要调用返回的生成器对象的next方法

    当函数内部代码, 遇到yield时会中断执行

// 1.定义了一个生成器函数
function* foo() {
  console.log("00001")
  console.log("00002")
  yield console.log("aaaaa")
  console.log("00003")
  console.log("00004")
  yield
  console.log("00005")
  console.log("00006")
}

// 2.调用生成器函数, 会返回一个生成器对象
const generator =  foo()
// 当遇到yield时 会执行到与yield右边的代码后中断 yield左边如果有代码不执行 例如上面第一个yield后面还有代码也会执行
generator.next() // 00001 00002 aaaaa
generator.next() // 00003 00004
generator.next() // 00005 00006
  1. 我们之前学习迭代器时,知道迭代器的next是会有返回值的, 而生成器也是一个特殊的迭代器, 那么我们看一下生成器调用next方法会返回什么
function* foo() {
  console.log("00001")
  console.log("00002")
  yield
  console.log("00003")
  console.log("00004")
  yield
  console.log("00005")
  console.log("00006")
}

// 生成器返回一个生成器
const generator =  foo()
console.log(generator.next()) // {value: undefined, done: false}
console.log(generator.next()) // {value: undefined, done: false}
console.log(generator.next()) // {value: undefined, done: true}
  1. 但是我们很多时候不希望next返回的是一个undefined,这个时候我们可以通过yield来返回结果;
function* foo() {
  console.log("00001")
  console.log("00002")
  yield "aaaaa"
  console.log("00003")
  console.log("00004")
  yield "bbbbb"
  console.log("00005")
  console.log("00006")
}

// 生成器返回一个生成器
const generator =  foo()
console.log(generator.next()) // {value: "aaaaa", done: false}
console.log(generator.next()) // {value: "bbbbb", done: false}
console.log(generator.next()) // {value: undefined, done: true}

1.3 生成器传递参数

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

  • 答案是可以的, 我们在调用next函数的时候,可以给它传递参数,那么这个参数会作为上一个yield语句的返回值
function* foo(name1) {
  console.log("00001", name1)
  console.log("00002", name1)
  const name2 = yield "aaaaa"
  console.log("00003", name2)
  console.log("00004", name2)
  const name3 = yield "bbbbb"
  console.log("00005", name3)
  console.log("00006", name3)
}

// 生成器返回一个生成器
const generator =  foo("name1")
// 第一次传递参数是通过函数传递
console.log(generator.next("name1"))

// 第二次及后面开始传递参数, 是通过yield
console.log(generator.next("name2"))
console.log(generator.next("name3"))

1.4 生成器提前结束

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

  • return传值后这个生成器函数就会结束,之后调用next不会继续生成值了
function* foo(name1) {
  console.log("00001", name1)
  console.log("00002", name1)
  const name2 = yield "aaaaa"
  console.log("00003", name2)
  console.log("00004", name2)
  const name3 = yield "bbbbb"
  console.log("00005", name3)
  console.log("00006", name3)
}

// 生成器返回一个生成器
const generator =  foo("name1")
console.log(generator.next("name1")) // {value: 'aaaaa', done: false}
// 通过return提前结束生成器
console.log(generator.return("name2")) // {value: 'name2', done: true}
// return之后再通过next调用不会继续生成值
console.log(generator.next("name3")) // {value: undefined, done: true}
console.log(generator.next()) // {value: undefined, done: true}
console.log(generator.next()) // {value: undefined, done: true}

1.5 生成器抛出异常

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

  • 抛出异常后我们可以在生成器函数中捕获异常
  • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行;
  • 目前未学习捕获异常语句, 了解即可
function* foo(name1) {
  console.log("00001", name1)
  console.log("00002", name1)
  const name2 = yield "aaaaa"
  console.log("00003", name2)
  console.log("00004", name2)
  const name3 = yield "bbbbb"
  console.log("00005", name3)
  console.log("00006", name3)
}

// 生成器返回一个生成器
const generator =  foo("name1")
console.log(generator.next("name1")) // {value: 'aaaaa', done: false}
console.log(generator.throw("name2 throw")) // Uncaught name2 throw
console.log(generator.return("name2"))
console.log(generator.next("name3"))

2.生成器应用

2.1 生成器替代迭代器

我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:

  • 我们前面学习迭代器的时候, 有写过下面这样一个案例用于实现一个迭代器
function createArrayIterator(arr) {
  let index = 0
  return {
    next: function() {
      if (index < arr.length) {
        return { done: false, value: arr[index++] }
      } else {
        return { done: true }
      }
    }
  }
}
  • 我们可以利用生成器替代迭代器进行优化
const names = ["aaa", "bbb", "ccc"]

function* createArrayIterator(arr) {
  for (let i = 0; i < arr.length; i++) {
    yield arr[i]
  }
}

const namesIterator = createArrayIterator(names)
console.log(namesIterator.next()) // {value: 'aaa', done: false}
console.log(namesIterator.next()) // {value: 'bbb', done: false}
console.log(namesIterator.next()) // {value: 'ccc', done: false}
console.log(namesIterator.next()) // {value: undefined, done: true}
  • 事实上我们还可以使用yield*来生产一个可迭代对象:这个时候相当于是一种yield的语法糖,只不过会依次迭代这个可迭代对象,每次迭代其中的一个值
const names = ["aaa", "bbb", "ccc"]

function* createArrayIterator(arr) {
    yield* arr
}

const namesIterator = createArrayIterator(names)
console.log(namesIterator.next()) // {value: 'aaa', done: false}
console.log(namesIterator.next()) // {value: 'bbb', done: false}
console.log(namesIterator.next()) // {value: 'ccc', done: false}
console.log(namesIterator.next()) // {value: undefined, done: true}

生成器案例: 定义一个生成器, 可以创建某个范围的值

  • 例如: 生成 [3, 9) 之间的值
function* createRangeGenerator(start, end) {
  for (let i = start; i < end; i++) {
    yield i
  }
}

const numsGenerator = createRangeGenerator(3, 9)
console.log(numsGenerator.next()) // {value: 3, done: false}
console.log(numsGenerator.next()) // {value: 4, done: false}
console.log(numsGenerator.next()) // {value: 5, done: false}
console.log(numsGenerator.next()) // {value: 6, done: false}
console.log(numsGenerator.next()) // {value: 7, done: false}
console.log(numsGenerator.next()) // {value: 8, done: false}
console.log(numsGenerator.next()) // {value: undefined, done: true}

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

在迭代器的时候我们创建过一个自定义类, 用于创建一系列可迭代对象 :

class Person {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }

  // 添加实例方法, 迭代器
  [Symbol.iterator] () {
    let index = 0
    return {
      next: () => {
        if (index < this.friends.length) {
          return { done: false, value: this.friends[index++] }
        } else {
          return { done: true }
        }
      }
    }
  }
}

// 这样Person类创建出来的对象都是可迭代对象
const p1 = new Person("kaisa", 18, ["aaa", "bbb", "ccc"])

// 可以进行for...of操作
for (item of p1) {
  console.log(item) // aaa bbb ccc
}

这个自定义类我们也可以换成生成器, 对上面代码优化:

class Person {
  constructor(name, age, friends) {
    this.name = name
    this.age = age
    this.friends = friends
  }

  // 添加实例方法, 迭代器前面加上*变为生成器
  *[Symbol.iterator] () {
    // yield*后面跟要迭代的对象
    yield* this.friends
  }
}

// 进行for...of操作
const p1 = new Person("kaisa", 18, ["aaa", "bbb", "ccc"])
for (item of p1) {
  console.log(item) // aaa bbb ccc
}

更多推荐

JS的生成器详细使用、生成器结合迭代器使用