迭代器生成器 | 青训营笔记

99 阅读3分钟

iterator generator

相信大家都对ES6中的async和await不陌生,发请求想必是天天用吧🪂,但是他们的原理其实是通过生成器函数实现的,所以本着要理解原理的原则,我决定把他俩的原理好好理一理。

迭代器

迭代器是让用户可在容器对象上遍历的对象,使用该接口无需关心对象的内部具体细节,其行为类似数据库中的光标,即帮助我们对某个数据结构进行遍历的对象
在JS中,迭代器是一个具体的对象,需要符合迭代器协议:这个对象要有一个next方法
next方法有如下要求:

  • 一个无参数或者接受一个参数的函数函数返回一个应当有以下两个属性的对象:
    • done(boolean)若迭代完毕则为true
    • value 迭代器返回的值
const createIterator = (arr) => {
  let index = 0
  return {
    next() {
      if(index < arr.length) {
        return {done: false, value: arr[index++]}
      } else {
        return {done: true, value: undefined}
      }
    }
  }
}

const names = ["Allen", "Tony", "Rose"]
const namesIterator = createIterator(names)
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())
console.log(namesIterator.next())

image.png

可迭代对象

他和迭代器是不同的东西,虽然都是对象。
当一个对象实现了iterable protocol协议时,他就是一个可迭代对象;这个对象的要求是必须实现@@iterator方法,在代码中通过Symbol.iterator访问(就是拥有迭代器的对象就是可迭代对象)

const iterableObj = {
  names: ["Allen", "Tony", "Rose"],
  [Symbol.iterator]: function() {
    let index = 0

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

const iterator = iterableObj[Symbol.iterator]()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

image.png拿到迭代器依然可以用next来用
那迭代器有什么用呢,平时我们经常使用for...of来进行遍历,但是for...of不能遍历对象

const obj = {
  name: "Allen",
  age: 18
}

for(let item of obj) {
  console.log(item)
}

image.png如果使用那就会报错
但若是使用刚才我们写好了迭代器的可迭代对象再使用for...of情况就会不一样

const iterableObj = {
  names: ["Allen", "Tony", "Rose"],
  [Symbol.iterator]: function() {
    let index = 0

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

for(let item of iterableObj) {
  console.log(item)
}

image.png他就可以直接遍历了,这就是可迭代对象
像数组这种就是自带迭代器的,就可以直接用for...of进行遍历

const nums = [1, 2, 3]

const iterator = nums[Symbol.iterator]()
console.log(iterator)
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())

image.png
遍历的过程可能会被提前终止,那在迭代器中也是可以监听到的

const iterableObj = {
  names: ["Allen", "Tony", "Rose"],
  [Symbol.iterator]: function() {
    let index = 0

    return {
      next: () => {
        if(index < this.names.length) {
          return {done: false, value: this.names[index++]}
        } else {
          return {done: true, value: undefined}
        }
      },
      return: () => {
        console.log("iterating break")
        return {done: true, value: undefined}
      }
    }
  }
}

for(const item of iterableObj) {
  if(item === "Tony") break
}

加一个return方法就可以实现监听提前终止

生成器

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行等
生成器函数也是一个函数,但是和普通的函数有一些区别

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

  const num1 = 1
  console.log(num1)
  yield

  const num2 = 2
  console.log(num2)
  yield

  const num3 = 3
  console.log(num3)
  yield

  console.log("end")
}

const generator = foo()

generator.next()
generator.next()
generator.next()
generator.next()

image.png每调用一次next可以执行一段yield前的代码(也包括yield那一行)
调用生成器函数的next会返回一个对象,在yield后面跟一个值可以赋值给这个对象的value

function* foo() {
  const num1 = 1
  yield num1

  const num2 = 2
  yield num2

  const num3 = 3
  yield num3

  return 123
}

const generator = foo()

console.log(generator.next())
console.log(generator.next())
console.log(generator.next())
console.log(generator.next())

image.png
另外如果我们把yield分割的不同片段代码称为第一段第二段等,这些片段的代码传递参数的方式有些特殊

function* foo(prop) {		//prop是第一个参数
  console.log("start")

  const num1 = 1
  console.log("first part", num1)
  console.log("first part", prop)
  const prop1 = yield num1		// 第二段的代码在上一个yield的返回值拿到

  const num2 = 2
  console.log("second part", num2)
  console.log("second part", prop1)
  const prop2 = yield num2

  const num3 = 3
  console.log("third part",num3)
  console.log("third part",prop2)
  yield num3

  console.log("end")
  return 123
}

const generator = foo(5)		// 第一个的参数在这里传

console.log(generator.next())
console.log(generator.next(10))		// 第二段代码的参数在这里传
console.log(generator.next(20))		// 第三个参数
console.log(generator.next())

image.png