最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 《javascript高级程序设计》学习笔记 | 7.3.生成器

    正文概述 掘金(simon9124)   2021-04-20   362

    生成器

    • ES6 新增的结构,可以在一个函数块内暂停和恢复代码执行,可以自定义迭代器实现协程

    相关代码 →

    生成器基础

    • 生成器的形式是一个函数,函数名称前加一个星号*
    • 可以定义函数的地方,都可以定义生成器(箭头函数除外
    function* generatorFn() {} // 生成器函数声明
    let gfn = function* () {} // 生成器函数表达式
    let foo = {
      *generatorFn() {}, // 生成器函数作为对象字面量方法
    }
    class Foo {
      *generatorFn() {} // 生成器函数作为类实例方法
    }
    class FooStatic {
      static *generatorFn() {} // 生成器函数作为类静态方法
    }
    
    • 调用生成器函数会产生一个生成器对象,生成器对象实现了 Iterator 接口,具有next()方法
    const g = generatorFn() // 调用生成器函数,产生生成器对象
    console.log(g) // generatorFn {<suspended>},生成器对象
    console.log(g.next) // 生成器对象具有next()方法
    
    • next()方法的返回值类似于迭代器,有done 属性value 属性
    • 函数体为空的生成器调用一次next()就会达到done:true状态
    console.log(g.next()) // { value: undefined, done: true },函数体为空
    
    • 可以通过生成器函数的返回值指定value的返回值(默认为 undefined)
    function* generatorFn2() {
      return 'foo'
    }
    const g2 = generatorFn2()
    console.log(g2.next()) // { value: 'foo', done: true }
    console.log(g2.next()) // { value: undefined, done: true },耗尽
    
    • 生成器函数只会在初次调用next()方法后开始执行
    function* generatorFn3() {
      console.log('生成器函数开始执行')
    }
    const g3 = generatorFn3() // 调用生成器函数,产生生成器对象(生成器函数还未执行,不打印日志)
    g3.next() // '生成器函数开始执行',初次调用next()方法,生成器函数开始执行,打印日志
    
    • 生成器对象实现了 Iterable 接口,其默认迭代器自引用
    function* generatorFn4() {}
    console.log(generatorFn4) // ƒ* generatorFn4() {},生成器函数
    
    const g4 = generatorFn4()
    console.log(g4) // generatorFn4 {<suspended>},生成器对象
    
    console.log(g4[Symbol.iterator]) // ƒ [Symbol.iterator]() { [native code] },迭代器工厂函数
    console.log(g4[Symbol.iterator]()) // generatorFn4 {<suspended>},迭代器
    
    console.log(g4 === g4[Symbol.iterator]()) // true,生成器对象的默认迭代器是自引用的
    

    通过 yield 中断执行

    • yield关键字可以让生成器停止开始执行:
      • 生成器遇到yield关键字之前正常执行
      • 遇到yield关键字后停止执行,函数作用域的状态被保留
      • 停止执行的生成器函数通过生成器对象调用next()方法恢复执行
    function* generatorFn5() {
      yield
    }
    let g5 = generatorFn5()
    console.log(g5.next()) // { value: undefined, done: false },yield生成的值
    console.log(g5.next()) // { value: undefined, done: true },恢复执行生成的值
    
    • yield关键字与函数的return语句作用相似,其生成的值会出现在next()方法返回的对象里,但done状态不同
      • 通过yield关键字退出的生成器函数会处在done:false状态
      • 通过return关键字退出的生成器函数会处在done:true状态
    function* generatorFn6() {
      yield 'foo'
      yield 'bar'
      return 'baz'
    }
    let g6 = generatorFn6()
    console.log(g6.next()) // { value: 'foo', done: false },yield关键字退出
    console.log(g6.next()) // { value: 'bar', done: false },yield关键字退出
    console.log(g6.next()) // { value: 'baz', done: true },return关键字退出
    
    • 同一个生成器函数的不同生成器对象之间没有联系,一个生成器对象上调用next()方法不影响其他生成器
    let g7 = generatorFn6() // 生成器对象g7
    let g8 = generatorFn6() // 生成器对象g8
    
    console.log(g7.next()) // { value: 'foo', done: false }
    console.log(g8.next()) // { value: 'foo', done: false }
    console.log(g8.next()) // { value: 'bar', done: false }
    console.log(g7.next()) // { value: 'bar', done: false }
    
    • yield关键字必须在生成器函数内部,直接位于生成器函数定义中使用,用在其他地方或嵌套的非生成器函数会报错
    function* validGeneratorFn() {
      yield 'result'
    }
    function* invalidGeneratorFnA() {
      function a() {
        yield 'result' // SyntaxError: Unexpected string
      }
    }
    function* invalidGeneratorFnB() {
      const b = () => {
        yield 'result' // SyntaxError: Unexpected string
      }
    }
    function* invalidGeneratorFnC() {
      ;(() => {
        yield 'result' // SyntaxError: Unexpected string
      })()
    }
    

    生成器对象作为可迭代对象

    • 把生成对象当成可迭代对象
    function* generatorFn7() {
      // 生成器函数
      yield 1
      yield 2
      yield 3
    }
    for (const x of generatorFn7()) {
      // 调用生成器函数generatorFn7,generatorFn7()是生成器对象
      console.log(x)
      /* 
        1
        2
        3
      */
    }
    
    • 使用生成器控制迭代循环的次数
    function* nTimes(n) {
      while (n--) {
        console.log(n)
        yield
      }
    }
    for (let _ of nTimes(3)) {
      console.log(_)
      /* 
        2,第1次循环n
        undefined,第1次循环yield
        1,第2次循环n
        undefined,第2次循环yield
        0,第3次循环n
        undefined,第3次循环yield
      */
    }
    

    使用 yield 实现输入和输出

    • 除了作为函数的中间返回语句使用,yield关键字还可以作为函数的中间参数使用
      • 上一次让生成器函数暂停的yield关键字会接收到传给next()方法的第一个值
      • 第一次调用next()传入的值不会被使用,因为仅仅是为了开始执行生成器函数
    function* generatorFn8() {
      console.log(yield)
      console.log(yield)
      console.log(yield)
    }
    let g9 = generatorFn8() // 调用生成器函数,产生生成器对象
    g9.next('bar') // 第一次调用next()的值不会被使用,仅作为开始执行生成器函数
    g9.next('baz') // 'baz',调用next()传入baz,参数作为交给同一个yield的值
    g9.next('qux') // 'qux',调用next()传入qux,参数作为交给同一个yield的值
    
    • yield关键字同时用于输入和输出(与return关键字同时使用)
      • next()方法没有参数,生成器函数直到遇到yield关键字停止执行
      • next()方法有参数,参数作为交给同一个 yield 的值,生成器函数执行return返回本次传入的值
    function* generatorFn9() {
      return yield 'foo'
    }
    let g10 = generatorFn9()
    console.log(g10.next()) // { value: 'foo', done: false },next()没有参数,遇到yield关键字暂停执行,并计算要产生的值
    console.log(g10.next('bar')) // { value: 'bar', done: true },next()有参数,参数作为交给同一个yield的值,相当于return 'bar'
    
    • yield关键字多次使用
    function* generatorFn10() {
      for (let i = 0; ; i++) {
        yield i
      }
    }
    let g11 = generatorFn10()
    console.log(g11.next()) // { value: 0, done: false }
    console.log(g11.next()) // { value: 1, done: false }
    console.log(g11.next()) // { value: 2, done: false }
    console.log(g11.next()) // { value: 3, done: false }
    
    • 根据迭代次数产生相应索引
    function* nTimes(n) {
      let i = 0
      while (n--) {
        yield i++
      }
    }
    for (let x of nTimes(3)) {
      console.log(x)
      /* 
        0
        1
        2
      */
    }
    
    • 使用生成器实现范围
    function* range(start, end) {
      while (end > start) {
        yield start++
      }
    }
    for (const x of range(4, 7)) {
      console.log(x)
      /* 
        4
        5
        6
      */
    }
    
    • 使用生成器填充数组
    function* zeros(n) {
      while (n--) {
        yield 0
      }
    }
    console.log(zeros(8)) // zeros {<suspended>},生成器对象
    console.log(Array.from(zeros(8))) // [0, 0, 0, 0, 0, 0, 0, 0],生成器对象作为可迭代对象
    
    • 使用生成器实现斐波那契数列
    function* fibonacci() {
      let arr = [0, 1]
      let [prev, curr] = arr
      while (true) {
        ;[prev, curr] = [curr, prev + curr]
        arr.push(curr)
        yield arr
      }
    }
    function Fibonacci(n) {
      if (n === 1) {
        // 第1项
        return 0
      } else if (n === 2 || n === 3) {
        // 第2、3项
        return 1
      } else {
        // 第4项或之后
        let num = 0
        const fibo = fibonacci()
        for (let i = 3; i <= n; i++) {
          num = fibo.next().value
        }
        return num
      }
    }
    console.log(Fibonacci(8).join()) // 0,1,1,2,3,5,8,13
    

    产生可迭代对象

    • 星号*增强yield,让其能够迭代一个可迭代对象,yield*将一个可迭代对象序列化为一连串单独产出的值
    function* generatorFn11() {
      yield* [1, 2, 3]
    }
    let g12 = generatorFn11()
    for (const x of generatorFn11()) {
      console.log(x)
      /* 
        1
        2
        3
      */
    }
    
    // 等价于
    function* generatorFn11() {
      for (const x of [1, 2, 3]) {
        yield x
      }
    }
    
    • yield*的值是**关联迭代器返回done:true**时value的属性:

      • 对于普通迭代器,done:true代表迭代器耗尽,这个值是 undefined
      function* generatorFn12() {
        console.log('iterValue', yield* [1, 2, 3])
      }
      for (const x of generatorFn12()) {
        console.log('value', x)
        /* 
        value 1
        value 2
        value 3
        iterValue undefined
      */
      }
      
      • 对于生成器函数产生的迭代器,done:true的值是return 返回的值(没有 return 值则返回 undefined)
      function* innerGeneratorFn() {
        yield 'foo'
        return 'bar'
      }
      function* outerGeneratorFn() {
        console.log('iterValue', yield* innerGeneratorFn())
      }
      for (const x of outerGeneratorFn()) {
        console.log('value', x)
        /* 
        value foo
        iterValue bar
      */
      }
      

    使用 yield*实现递归算法

    • yield*实现递归,此时生成器可以产生自身
    function* nTimes(n) {
      if (n > 0) {
        yield* nTimes(n - 1) // 生成器对象作为可迭代对象
        yield n
      }
    }
    for (const x of nTimes(3)) {
      console.log(x)
      /*
        1
        2
        3
      */
    }
    

    生成器作为默认迭代器

    • 生成器对象实现了Iterable接口,生成器函数默认迭代器被调用之后都产生迭代器
    class Foo2 {
      // Foo既是迭代器,又是生成器函数
      constructor() {
        this.values = [1, 2, 3]
      }
      *[Symbol.iterator]() {
        yield* this.values
      }
    }
    const f = new Foo2() // 产生可迭代的生成器对象
    for (const x of f) {
      console.log(x)
      /* 
        1
        2
        3
      */
    }
    

    提前终止生成器

    • 一个实现Iterator接口的对象一定有next()方法,还有一个可选的return()方法,生成器还有第三个方法throw()
    • return()throw()都可以用于强制生成器进入关闭状态
    function* generatorFn13() {}
    let g13 = generatorFn13() // 生成器对象
    
    console.log(g13.next) // ƒ next() { [native code] }
    console.log(g13.return) // ƒ return() { [native code] }
    console.log(g13.throw) // ƒ throw() { [native code] }
    

    return

    return()方法返回种种迭代器对象的值(即 return()方法的参数)

    function* generatorFn14() {
      yield* [1, 2, 3]
    }
    let g14 = generatorFn14()
    
    console.log(g14) // generatorFn14 {<suspended>}
    console.log(g14.return(5)) // {value: 5, done: true}
    console.log(g14) // generatorFn14 {<closed>}
    
    • 通过return()方法进入关闭状态的生成器对象,后续调用next()都会显示done:true状态,后续提供的任何返回值都不再被存储或传播
    console.log(g14.next()) // { value: undefined, done: true },已经调用过return()
    console.log(g14.next()) // { value: undefined, done: true }
    console.log(g14.next()) // { value: undefined, done: true }
    
    • for-of等内置语言结构会忽略状态为done:true的迭代器对象内部返回的值(忽略 undefined)
    let g15 = generatorFn14()
    for (const x of g15) {
      x > 1 && g15.return() // x大于1则停止生成器
      console.log(x)
      /* 
        1
        2
        自动忽略done:true返回的value(undefined)
      */
    }
    

    throw

    • throw()方法会在暂停的时候,将一个提供的错误注入到生成器对象中

      • 如果错误未被处理,则生成器关闭
      function* generatorFn15() {
        yield* [1, 2, 3]
      }
      let g16 = generatorFn15()
      
      console.log(g16) // generatorFn15 {<suspended>}
      try {
        g16.throw('foo') // 注入错误
      } catch (err) {
        console.log(err) // 'foo'
      }
      console.log(g16) // generatorFn15 {<closed>},错误未被处理,生成器关闭
      
      • 如果错误在生成器函数内部处理,则生成器不会关闭,且可以恢复执行;错误处理会跳过对应的yield
      function* generatorFn16() {
        for (const x of [1, 2, 3]) {
          // 错误在生成器的try/catch块中抛出 -> (生成器对象已开始执行)在生成器内部被捕获
          // 若生成器对象未开始执行,则throw()抛出的错误不会在生成器内部被捕获
          try {
            yield x // 在yield关键字处暂停执行
          } catch (err) {
            console.log(err) // 'foo'
          }
        }
      }
      let g17 = generatorFn16()
      
      console.log(g17.next()) // { value: 1, done: false }
      g17.throw('foo') // 注入错误
      console.log(g17.next()) // { value: 3, done: false },跳过对应的yield
      

    总结 & 问点

    • 什么是生成器?哪些函数可以定义生成器?
    • 如何获取生成器对象?如何指定生成器 next()方法的 value 返回值?生成器函数什么时候开始执行?
    • 如何理解“生成器对象的默认迭代器是自引用”的?
    • yield 关键字在生成器中的作用是什么?其和 return 关键字的返回值有什么不同
    • 同一个生成器方法生成的不同生成器对象之间有联系么?
    • 请使用生成器函数和 yield 关键字,分别用代码实现以下功能:
      • 迭代 5 次,获取每次迭代值和索引
      • 获取大于 3 小于 9 的整数
      • 从 1 开始,填充长度为 6 的自增数组
      • 求出斐波那契数列第 20 项数字的值(从 0 算起)
    • yield*的作用是什么?在普通迭代器、生成器函数产生的迭代器中,yield*的值分别是什么?
    • 如何将生成器作为默认迭代器?return()和 throw()方法提前终止生成器的用法分别是什么?

    起源地下载网 » 《javascript高级程序设计》学习笔记 | 7.3.生成器

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元