迭代器-生成器详解

133 阅读5分钟

一、迭代器Iterator

1.1 什么是迭代器, 创建迭代器

  • 在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议(iterator protocol)

    • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式
    • 在JavaScript中这个标准就是一个特定的next方法
  • next方法的要求: 一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:

    • done(boolean)
      • 如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
      • 如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。
    • value
      • 迭代器返回的任何 JavaScript 值。done 为 true 时可省略。
    const names = ["abc", "cba", "nba"]
    
    // 给数组names创建一个迭代器(迭代器: names的迭代器)
    let index = 0
    const namesIterator = {
      next: function() {
        // done: Boolean
        // value: 具体值/undefined
        if (index < names.length) {
          return { done: false, value: names[index++] }
        } else {
          return { done: true }
        }
      }
    }
    
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    console.log(namesIterator.next())
    
    // 数组nums
    const nums = [100, 24, 55, 66, 86]
    
    let indexNum = 0
    const numsIterator = {
      next: function() {
        // done: Boolean
        // value: 具体值/undefined
        if (indexNum < nums.length) {
          return { done: false, value: nums[indexNum++] }
        } else {
          return { done: true }
        }
      }
    }
    
  • 为数组创建迭代器

    const names = ["abc", "cba", "nba"]
    
    // 封装一个函数
    function createArrayIterator(arr) {
      let index = 0
      return {
        next: function() {
          if (index < arr.length) {
            return { done: false, value: arr[index++] }
          } else {
            return { done: true }
          }
        }
      }
    }
    
    const namesIterator = createArrayIterator(names)
    console.log(namesIterator.next()) // {done: false, value: 'abc'}
    console.log(namesIterator.next()) // {done: false, value: 'cba'}
    console.log(namesIterator.next()) // {done: false, value: 'nba'}
    console.log(namesIterator.next()) // {done: false}
    

1.2 什么是可迭代对象

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

  • 认识可迭代对象

    // 将infos变成一个可迭代对象
    /*
      1.必须实现一个特定的函数: [Symbol.iterator]
      2.这个函数需要返回一个迭代器(这个迭代器用于迭代当前的对象)
    */
    const infos = {
      friends: ["kobe", "james", "curry"],
      [Symbol.iterator]: function() {
        let index = 0
        const infosIterator = {
          next: function() {
            // done: Boolean
            // value: 具体值/undefined
            if (index < infos.friends.length) {
              return { done: false, value: infos.friends[index++] }
            } else {
              return { done: true }
            }
          }
        }
        return infosIterator
      }
    }
    
    // 可迭代对象必然具备下面的特点
    // const iterator = infos[Symbol.iterator]()
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    // console.log(iterator.next())
    
    // 可迭对象可以进行for of操作
    for (const item of infos) {
      console.log(item)
    }
    
    // 可迭代对象必然有一个[Symbol.iterator]函数
    // 数组是一个可迭代对象
    const students = ["张三", "李四", "王五"]
    console.log(students[Symbol.iterator])
    const studentIterator = students[Symbol.iterator]()
    console.log(studentIterator.next()) // {value: '张三', done: false}
    console.log(studentIterator.next()) // {value: '李四', done: false}
    console.log(studentIterator.next()) // {value: '王五', done: false}
    console.log(studentIterator.next()) // {value: undefined, done: true}
    
  • 可迭代对象:迭代对象中的 key和value

    const infos = {
      name: "lilei",
      age: 18,
      height: 1.78,
    
      [Symbol.iterator]: function() {
        // const keys = Object.keys(this)
        // const values = Object.values(this)
        const entries = Object.entries(this)
        let index = 0
        const iterator = {
          next: () => {
            if (index < entries.length) {
              return { done: false, value: entries[index++] }
            } else {
              return { done: true }
            }
          }
        }
        return iterator
      }
    }
    
    // 可迭对象可以进行for of操作
    for (const item of infos) {
      const [key, value] = item
      console.log(key, value)
    }
    

1.3 迭代器类型

  • 很多原生对象已经实现了可迭代协议,会生成一个迭代器对象

    • 数组/String/Set/Map/argumetns/NodeList
    const set = new Set(["abc", "cba", "nba"])
    for (const item of set) {
      console.log(item)
    }
    const setIterator = set[Symbol.iterator]()
    console.log(setIterator.next())
    console.log(setIterator.next())
    console.log(setIterator.next())
    console.log(setIterator.next())
    

1.4 可迭代对象的应用

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

    // 1.用在特定的语法上
    const names = ["abc", "cba", "nba"]
    const info = {
      name: "kobe",
      age: 24,
      height: 1.88,
      [Symbol.iterator]: function() {
        const values = Object.values(this)
        let index = 0
        const iterator = {
          next: function() {
            if (index < values.length) {
              return { done: false, value: values[index++] }
            } else {
              return { done: true }
            }
          }
        }
        return iterator
      }
    }
    
    function foo(arg1, arg2, arg3) {
      console.log(arg1, arg2, arg3)
    }
    
    foo(...info)
    
    // 2.一些类的构造方法中, 也是传入的可迭代对象
    const set = new Set(["aaa", "bbb", "ccc"])
    const set2 = new Set("abc")
    console.log(set2)
    const set3 = new Set(info)
    console.log(set3)
    
    // 3.一些常用的方法
    const p1 = Promise.resolve("aaaa")
    const p2 = Promise.resolve("aaaa")
    const p3 = Promise.resolve("aaaa")
    const pSet = new Set()
    pSet.add(p1)
    pSet.add(p2)
    pSet.add(p3)
    Promise.all(pSet).then(res => {
      console.log("res:", res)
    })
    
    function bar() {
      // console.log(arguments)
      // 将arguments转成Array类型
      const arr = Array.from(arguments)
      console.log(arr)
    }
    
    bar(111, 222, 333)
    

1.5 自定义类的可迭代对象

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

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

const p = new Person("kobe", 30, 1.98, ["curry", "james", "aonier", "weide"])

for (const item of p) {
  console.log(item)
}

1.6 迭代器的中断执行(了解)

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

    • 比如遍历的过程中通过breakreturnthrow中断了循环操作
    • 比如在解构的时候,没有解构所有的值
  • 想要监听中断的话,可以添加return方法:

    [Symbol.iterator]() {
        const values = Object.keys(this)
        let index = 0
        const iterator = {
          next: () => {
            if (index < values.length) {
              return { done: false, value: values[index++] }
            } else {
              return { done: true }
            }
          },
          return: () => {
            // console.log("监听到迭代器中断了")
            return { done: true }
          }
        }
        return iterator
      }
    

二、生成器Generator

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

2.1 生成器函数和生成器对象

  • 生成器函数:

    • function *
    • 内部可以通过yield控制函数的执行
    • 返回生成器对象(是一种特殊的迭代器)
      • MDN:Instead, they return a special type of iterator, called a Generator.
  • 生成器对象:特殊的迭代器

    /*
      生成器函数: 
        1.function后面会跟上符号: *
        2.代码的执行可以被yield控制
        3.生成器函数默认在执行时, 返回一个生成器对象
          * 要想执行函数内部的代码, 需要生成器对象, 调用它的next操作
          * 当遇到yield时, 就会中断执行
    */
    
    // 1.定义了一个生成器函数
    function* foo() {
      console.log("1111")
      console.log("2222")
      yield
      console.log("3333")
      console.log("4444")
      yield
      console.log("5555")
      console.log("6666")
    }
    
    // 2.调用生成器函数, 返回一个 生成器对象
    const generator = foo()
    // 调用next方法
    generator.next()
    generator.next()
    generator.next()
    

2.2 生成器的返回值和参数

  • 函数执行

    • 生成器函数通过next()来执行
    • 迭代器的next是会有返回值的,有时候不希望next返回的是一个undefined,这个时候可以通过yield来返回结果
  • 传递参数-next函数

    function* foo(name1) {
      console.log("执行内部代码:1111", name1)
      console.log("执行内部代码:2222", name1)
      const name2 = yield "aaaa"
      console.log("执行内部代码:3333", name2)
      console.log("执行内部代码:4444", name2)
      const name3 = yield "bbbb"
      // return "bbbb"
      console.log("执行内部代码:5555", name3)
      console.log("执行内部代码:6666", name3)
      yield "cccc"
      return undefined
    }
    
    // 2.调用生成器函数, 返回一个 生成器对象
    const generator = foo("next1")
    // 调用next方法
    // console.log(generator.next()) // { done: false, value: "aaaa" }
    // console.log(generator.next()) // { done: false, value: "bbbb" }
    // console.log(generator.next()) // { done: false, value: "cccc" }
    // console.log(generator.next()) // {done: true, value: undefined}
    
    // 3.在中间位置直接return, 结果
    // console.log(generator.next()) // { done: false, value: "aaaa" }
    // console.log(generator.next()) // { done: true, value: "bbbb" }
    // console.log(generator.next()) // { done: true, value: undefined }
    // console.log(generator.next()) // { done: true, value: undefined }
    // console.log(generator.next()) // { done: true, value: undefined }
    // console.log(generator.next()) // { done: true, value: undefined }
    
    // 4.给函数每次执行的时候, 传入参数
    console.log(generator.next())
    console.log(generator.next("next2"))
    console.log(generator.next("next3"))
    // console.log(generator.next())
    

2.3 提前结束return/throw(了解)

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

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

    • 抛出异常后可以在生成器函数中捕获异常
    • 但是在catch语句中不能继续yield新的值了,但是可以在catch语句外使用yield继续中断函数的执行
    function* foo(name1) {
      console.log("执行内部代码:1111", name1)
      console.log("执行内部代码:2222", name1)
      const name2 = yield "aaaa"
      console.log("执行内部代码:3333", name2)
      console.log("执行内部代码:4444", name2)
      const name3 = yield "bbbb"
      // return "bbbb"
      console.log("执行内部代码:5555", name3)
      console.log("执行内部代码:6666", name3)
      yield "cccc"
    
      console.log("最后一次执行")
      return undefined
    }
    
    const generator = foo("next1")
    
    // 1.generator.return提前结束函数
    // console.log(generator.next())
    // console.log(generator.return("next2"))
    // console.log("-------------------")
    // console.log(generator.next("next3"))
    // console.log(generator.next("next4"))
    
    // 2.generator.throw向函数抛出一个异常
    console.log(generator.next())
    console.log(generator.throw(new Error("next2 throw error")))
    console.log("-------------------")
    console.log(generator.next("next3"))
    console.log(generator.next("next4"))
    

2.4 generator替代iterator

  • createArray方法

  • createRange方法

    const names = ["abc", "cba", "nba"]
    const nums = [100, 22, 66, 88, 55]
    
    function* createArrayIterator(arr) {
      for (let i = 0; i < arr.length; i++) {
        yield arr[i]
      }
      // yield arr[0]
      // yield arr[1]
      // yield arr[2]
      // return undefined
    }
    
    // const namesIterator = createArrayIterator(names)
    // console.log(namesIterator.next())
    // console.log(namesIterator.next())
    // console.log(namesIterator.next())
    // console.log(namesIterator.next())
    
    // const numsIterator = createArrayIterator(nums)
    // console.log(numsIterator.next())
    // console.log(numsIterator.next())
    // console.log(numsIterator.next())
    // console.log(numsIterator.next())
    // console.log(numsIterator.next())
    // console.log(numsIterator.next())
    
    // 2.生成器函数, 可以生成某个范围的值
    // [3, 9)
    function* createRangeGenerator(start, end) {
      for (let i = start; i < end; i++) {
        yield i
      }
    }
    
    const rangeGen = createRangeGenerator(3, 9)
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    console.log(rangeGen.next())
    
  • 语法糖:

    • yield*
    • 会依次迭代这个可迭代对象,每次迭代其中的一个值

      const names = ["abc", "cba", "nba"]
      
      function* createArrayIterator(arr) {
        yield* arr
      }
      
      const namesIterator = createArrayIterator(names)
      console.log(namesIterator.next())
      console.log(namesIterator.next())
      console.log(namesIterator.next())
      console.log(namesIterator.next())
      
      class Person {
        constructor(name, age, height, friends) {
          this.name = name
          this.age = age
          this.height = height
          this.friends = friends
        }
      
        // 实例方法
        *[Symbol.iterator]() {
          yield* this.friends
        }
      }
      
      const p = new Person("kobe", 30, 1.98, ["curry", "james", "aonier", "weide"])
      for (const item of p) {
        console.log(item)
      }
      
      const pIterator = p[Symbol.iterator]()
      console.log(pIterator.next())
      console.log(pIterator.next())
      console.log(pIterator.next())
      console.log(pIterator.next())