(ES6) Generator 函数详解

一、简介


js中普通函数一旦开始执行,就会运行到最后或遇到return时结束,运行期间不会有其它代码能够打断它,也不能从外部再传入值到函数体内,而Generator函数(生成器)的出现使得打破函数的完整运行成为了可能,它执行期间可以返回多个值,可以做到走走停停,其语法行为与传统函数完全不同,它是ES6提供的一种异步编程解决方案,形式上也是一个普通函数,但有几个显著的特征:

  • function关键字与函数名之间有一个星号 “*” (推荐紧挨着function关键字)
  • 函数体内使用 yield 表达式,定义不同的内部状态 (可以有多个yield)
  • 直接调用 Generator函数并不会执行,也不会返回运行结果,而是返回一个遍历器对象(Iterator Object)
  • 依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态,该方法调用得到的是一个对象结果,包含两个属性(value,done),value表示此时遇到的yield前面表达式的结果,如果没有值就返回underfind

二、初识Generator(与普通函数对比)

 // 传统函数
  function foo() {
    return 'hello world'
  }

  foo()   // 'hello world',一旦调用立即执行


  // Generator函数
  function* generator() {
    yield 'status one'         // yield 表达式是暂停执行的标记  
    return 'hello world'
  }

  let iterator = generator()   // 调用 Generator函数,函数并没有执行,返回的是一个Iterator对象
  iterator.next()              // {value: "status one", done: false},value 表示返回值,done 表示遍历还没有结束
  iterator.next()              // {value: "hello world", done: true},value 表示返回值,done 表示遍历结束

上面的代码中可以看到传统函数和Generator函数的运行是完全不同的,传统函数调用后立

即执行并输出了返回值;Generator函数则没有执行而是返回一个Iterator对象,并通过调用Iterator对象的next方法来遍历,函数体内的执行看起来更像是“被人踢一脚才动一下”的感觉

通过刚才的例子应该有了初步的认识,那好,我们再来看一个例子

 function* gen() {
    yield 'hello'
    yield 'world'
    return 'ending'
  }

  let it = gen()

  it.next()   // {value: "hello", done: false}
  it.next()   // {value: "world", done: false}
  it.next()   // {value: "ending", done: true}
  it.next()   // {value: undefined, done: true}

上面代码中定义了一个 Generator函数,其中包含两个 yield 表达式和一个 return 语句(即产生了三个状态)

每次调用Iterator对象的next方法时,内部的指针就会从函数的头部或上一次停下来的地方开始执行,直到遇到下一个 yield 表达式或return语句暂停。换句话说,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而 next方法可以恢复执行
 

执行过程如下:

第一次调用next方法时,内部指针从函数头部开始执行,遇到第一个 yield 表达式暂停,并返回当前状态的值 ‘hello’(也就是刚才说的yield前面表达式的值)

第二次调用next方法时,内部指针从上一个(即第一个) yield 表达式开始,遇到第二个 yield 表达式暂停,返回当前状态的值 ‘world’

第三次调用next方法时,内部指针从第二个 yield 表达式开始,遇到return语句暂停,返回当前状态的值 ‘end’,同时所有状态遍历完毕,done 属性的值变为true

第四次调用next方法时,由于函数已经遍历运行完毕,不再有其它状态,因此返回 {value: undefined, done: true}。如果继续调用next方法,返回的也都是这个值

三、yield表达式

1、 yield 表达式只能用在 Generator 函数里面,用在其它地方都会报错

{
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
// 在一个普通函数中使用yield表达式,结果产生一个句法错误
}

2、yield 表达式如果用在另一个表达式中,必须放在圆括号里面

{
function* demo() {
console.log(‘Hello’ + yield); // SyntaxError
console.log(‘Hello’ + yield 123); // SyntaxError
console.log(‘Hello’ + (yield)); // OK
console.log(‘Hello’ + (yield 123)); // OK
}
}

3、yield 表达式用作参数或放在赋值表达式的右边,可以不加括号

{
function* demo() {
foo(yield ‘a’, yield ‘b’); // OK
let input = yield; // OK
}
}

4、yield 表达式和return语句的区别

相似:
        都能返回紧跟在语句后面的那个表达式的值
区别:
        每次遇到 yield,函数就暂停执行,下一次再从该位置继续向后执行;而 return 语句不具备记忆位置的功能一个函数只能执行一次 return 语句,而在 Generator 函数中可以有任意多个 yield

 现在我们再来看一个容易出错的例子。

  function* gen() {
    let result = yield 3 + 5
    console.log(result)
    yield result
  }

  let it = gen()
  console.log(it.next())      // {value: 8, done: false}
  console.log(it.next())      // undefined    {value: undefined, done: false}
  console.log(it.next())      // {value: undefined, done: true}

代码解析:以下解析为个人理解,有误之处,欢迎批评指正!
当执行第一个next方法时,代码执行到yield 3+5处暂停,此时因为yield后面的表达式有值,值为8,所以此时返回的对象的value值就为8,done为false(还未执行完),代码继续执行,当执行到第二个next方法时,由于next方法中没有传值,所以result就没有接收到值,于是乎返回的result结果就是undefined,done为false,第三次执行next方法时,代表generator函数已经执行完了,后续再执行也都是这个结果。

四、next方法

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值,所以说在第一次使用next方法时,传递参数是无效的。

从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数

function* gen() {
    let result = yield 3 + 5 + 6
    yield result
  }

  let it = gen()
  console.log(it.next(10))      //  {value: 14, done: false}
  console.log(it.next(3))      //  {value: 3, done: false}
}

五、总结

其实你可以把实际生活中的例子和generator关联起来,比如我给大家举个最简单的例子,便于大家理解:

图解: 其实整个generator函数执行过程也可以理解为是做菜的过程,generator函数()中的形参,可以理解为刚买回来的菜,洗菜、切菜、炒菜就是中间的一个个过程,干净的菜、切好的菜就是yield停下来产生的中间半成品结果,最终炒好的菜就可以理解为是return返回的最终结果。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值