美文网首页
JavaScript - Generator

JavaScript - Generator

作者: 人话博客 | 来源:发表于2018-12-09 01:30 被阅读0次

    Generator 英文翻译是 生产者,产生者.

    定义方式如下.

    function *gen() {
        //xxxxxx
        yield xxxx
        //xxxxx
        let a = yield xxxx.
        
        return xxx
    
    }
    
    • 使用 function * 来定义
    • 在此方法内部可以使用 yield & return

    Generator 函数和普通函数的区别

    一个普通函数,如果一旦执行了,就会从头走到尾,不会中止.

    function normalFn() {
        console.log('start')
        console.log('middle')
        console.log('end')
    }
    
    normalFn()
    
    普通函数从头走到尾,不会中断

    同样的代码放在generator函数里面

    function *gen() {
        console.log('start')
        yield
        console.log('middle')
        yield
        console.log('end')
    }
    
    // 返回一个枚举器
    let g = gen()
    g.next() // 输出 'start'
    g.next() // 输出 'middle'
    g.next() // 输出 'end'
    
    

    发现代码必须手动的调用gen()返回的枚举器的next()方法,代码才会执行.

    其中有 yield 这个关键字,yield的英文意思是:放弃

    用一张图去理解的话:

    感性的理解yield

    也就是说,每次调用 next() 方法,gen 函数里的代码就会往下走一步,每次走到一个 yield 就停止执行.

    generator内部通过yield关键字,将代码中断,除非你调用next(),否则,generator函数不会继续往下执行.


    关于 yield 关键字

    generator方法,返回一个枚举器.
    每次调用 next() 在碰到 yield 的时候,会停止运行,执行 yield 后面的表达式,并返回.

    function *__gen() {
        yield 'hello'
        yield 'world'
    }
    
    let g = __gen()
    
    console.log(g.next()) // {value:'hello',done:false}
    console.log(g.next()) // {value:'world',done:false}
    console.log(g.next()) // {value:undefined,done:true}
    
    
    next()方法返回了一个对象,包含value&done两个属性

    那第一个例子,单纯的使用 yield ,后面没有接任何表达式,返回的对象的valueundefined吗?

    
    function *gen() {
      console.log('start')
      yield
      console.log('middle')
      yield
      console.log('end')
    }
    
    // // 返回一个枚举器
    let g = gen()
    // g.next() // 输出 'start'
    // g.next() // 输出 'middle'
    // g.next() // 输出 'end'
    
    console.log(g.next()) //{value:undefined,done:false}
    console.log(g.next()) //{value:undefined,done:false}
    console.log(g.next()) //{value:undefined,done:true}
    
    yield后面没跟表达式 value就=undefined

    也就是说,next() 会返回一个对象,对象的.value 属性就是 yield 后面表达式返回的值.

    yield可以返回值:next()==>{value:yield表达式,done:boolean}

    yield 关键字,将 generator中断,并返回yield后面表达式的值赋值给next(){value属性}(如果有),
    并等待下一次的 next()


    关于 generator 里写 return 关键字

    我们发现在 generator 中,如果有 nyield,我们就需要手动的调用 n+1next() 才能得到 done:true 的结果.

    但是最后一次的next() 返回的对象中 .value=undefined..

    我们也可以使用在generator 中使用 return 关键字的方式来告知generator迭代结束,并将 return 返回的给过,给最后一个 next() 返回对象的value 属性.

    function *__gen() {
        yield 'hello'
        yield 'world'
        yield '!!!'
    }
    
    let g = __gen()
    console.log(g.next()) // {value:'hello',done:false}
    console.log(g.next()) // {value:'world',done:false}
    console.log(g.next()) // {value:'!!!',done:true}
    

    [图片上传失败...(image-2417dc-1544290199232)]

    如果不想让最后一次 next() 的 value 是 undefined ,而是想具体的返回一个值,那么就在 generator 里使用 return 关键字.

    两个作用

    • 告知 generator 迭代结束
    • 把return的值,给最后一个迭代返回对象的.value属性.

    模拟一下 generator 返回对象的next()方法.

    我是使用 next() 方法,来每次执行一段一直到 yield 的代码段.

    next() 方法每次回返回一个对象,包括 valuedone 两个属性.

    其中,valueyield 后面表达式的值. done 表示迭代是否结束.

    generator 很高级,在代码级别实现了中断.

    但我们也可以使用数组,模拟一个 next() 执行过程,来加深对迭代的理解.

    准备在数组上模拟一个generator中的next()方法.

    核心就两个

    • 每一次返回对应的元素 value
    • 迭代是否完成 done
    Array.prototype.next = function () {
      this.index = this.index || 0
      return {
        value: (this.index < this.length ? this[this.index] : undefined),
        done: this.index++ >= this.length ? true : false
      }
    }
    
      ;
    let arr = [1, 2, 3]
    console.log (arr.next())
    console.log (arr.next())
    console.log (arr.next())
    console.log (arr.next())
    
    用数组模拟generator的next()方法

    关于 next() 方法的参数问题.

    我们现在知道了,每次调用 next()方法

    • 都会执行到下一个 yield 停止

    • 返回停止yield后面的表达式结果.

    function *__gen() {
        console.log('第一阶段')
        yield 'hello'
        console.log('第二阶段')
        yield 'world'
        
        return '!!!' // 告知 generator 迭代结束,并把 !!! 传递给最后一次next() 返回对象的 .value 属性
    }
    
    
    let g = __gen()
    
    var res1 = g.next()
    res1.value === 'hello' // true
    var res2 = g.next()
    res2.value === 'world' // true
    var res3 = g.next()
    res3.value === '!!!'  // true & {done:true}
    
    

    那么既然 next() 是一个方法,方法是可以传递参数的.
    就像在定义 generator 方法传递参数一样.

    function *__gen(p1,p2) {
        console.log(p1,p2)
    }
    
    let g = __gen(1,2)
    g.next() // 输出 1,2 {value:undefined,done:true}
    
    

    next() 方法的参数是如何传递的呢?

    可以先看一段代码.

    function *__gen() {
        let a = yield 'hello'
        console.log(`a:${a}`)
        let b = yield 'world'
        console.log(`b:${b}`)
        return '!!!'
    }
    
    let g = __gen()
    g.next()
    g.next()
    g.next()
    

    先感性上猜测一下:

    let a = yield 'hello'

    ahello 吗?

    我们之前说过,yield 后面返回的值,给next()返回的对象的.value了.
    所以,这里的 a 是不是 hello 不好说.

    同理 b 是不是 world 也不好说.

    按照我们以前的理解, let xx = xxx 都是把表达式右边的结果返回给左边.

    在这里也会是这样吗?

    undefined & undefined

    突然发现,之前的想法全是错的.出来的是undefined,undefined

    hello,world 哪去了?

    记得之前说的吗?

    yield 的返回值,给对象了.

    console.log(g.next())
    console.log(g.next())
    console.log(g.next())
    
    yield返回的数据在next()方法返回对象的.value身上

    所以,let a = yield 'hello' 这里的 a 和后面的 hello 没有一毛钱关系.

    let a 和 yield 'hello' 没有一毛钱关系

    最终结果输出的是两个 undefined .
    那generator里写的 let a,let b 有啥意义啊?
    yield 后面的值给的又不是它们.


    next 也是一个函数,既然是函数,我们就可以给它传递参数.

    js 里的人一个函数,我们可以传递参数,而不管之前的函数是如何定义的.

    function noExplicitParam() {
        console.log(arguments[0])
        console.log(arguments[1])
    }
    
    noExplicitParam(1,2)
    
    
    js的函数参数很灵活

    所以,先不管 generator 的返回对象的 next() 方法是如何定义的.
    我们就传参数试试.

    function *__gen() {
      let a = yield 'hello'
      console.log(`a:${a}`)
      let b = yield 'world'
      console.log(`b:${b}`)
      return '!!!'
    }
    
    
    let g = __gen()
    g.next(1)
    g.next(2)
    g.next(3)
    

    还是先猜一下.

    • 当执行第一个 g.next(1) 碰到了 yield 'hello' , 代码终止,hello给了对象的.value , 1 给了 变量 a,
    • 当执行第二个g.next(2),碰到了 yield 'world',代码终止,world给了对象的.value,2给了b
    • 3由于后面没有let c 去接收,所以不会输出.

    所以,输出应该是 a:1 b:2

    查看结果:

    输出结果 a:2 b:3

    居然输出的是a:2 b:3,我滴妈啊.那第一个g.next(1)传递的1哪去了?????

    总是有惊喜.....

    然而实际情况是:

    • 每次调用next()方法,都会执行到一个yield结束.
      • 返回 yield后面的表达式给对象的.value(如果有)
      • 代码暂停,等待下一次的 next()

    上面只说了表达式右边的yield情况,对左边的let x 没有丝毫提起.

    其实这也是为什么 1 消失了的原因.

    使用大神们的博客说明:

    上面介绍的next方法均没有参数,其实可以为它提供一个参数。参数会被当作上一个yield语句的返回值。如果没有参数,那默认上一个yield语句的返回值为undefined。

    反正我是看起来比较费劲.

    一张我自己理解的图来说明为什么 1 消失了.

    yield的有效范围

    回忆以下之前说的,每一次执行到next()程序都会中止,等待下一个next()

    • 当执行到红色矩形的g.next(1) 时, 执行的是上述红色范围的区域.

      • 由于 let a = 不属于第一个 yield 的区域,所以 g.next(1) 参数 1 压根就没有变量去接收.
    • 当执行到蓝色矩形的g.next(2)时,执行的是上述蓝色范围的代码段.

      • 由于 let a = 属于蓝色代码的区域,属于第二个 yield 的有效范围,所以 2 就被赋值到 a .输出结果是 a:2
    • 当执行到紫色矩形 g.next(3)时,执行的是上述紫色代码的区域.

      • 由于let b = 属于紫色代码的有效区域,属于第三个yield的有效范围,所以 3 赋值给了 b,最终输出结果是 b:3

    所以,最终输出结果是 a:2,b:3 而不是 a:1,b:2.

    每一次next()的执行,真真正正的只到yield而已.(知道yield的右边,yield的左边数与下一个yield的有效范围)

    只要正确的识别了 yield 的有效区域,就能很快的知道 next() 是如何传递参数的.

    如果写这么一代码,就很好理解了.

    function *__gen() {
        console.log('第一个yield的有效范围开始')
        yield '第一个yield的有效范围结束'
        
        console.log('第二个yield的有效范围开始')
        yield '第二个yield的有效范围结束'
        
        console.log('第三个yield的有效范围开始')
        yield '第三个yield的有效范围结束'
        
        console.log('我在第四个yield的有效范围开始')
        yield '第四个yield的有效范围结束'
        
    }
    

    不包括yield这一行代码的左边。

    相关文章

      网友评论

          本文标题:JavaScript - Generator

          本文链接:https://www.haomeiwen.com/subject/trfhhqtx.html