JavaScript生成器

生成器是ES6新增的一个灵活结构,拥有在一个函数块内暂停和恢复代码执行的能力。这种能力具有深远的影响,比如,使用生成器可以自定义迭代器和实现协程。

生成器基础

  • 生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器。只要可以定义函数的地方就可以定义生成器。
  // 生成器函数声明
  function* generatorFn() {}

  // 生成器函数表达式
  let generatorFn = function * () {}

  // 作为对象字面量方法的生成器函数
  let foo = {
    * generatorFn() {}
  }

  // 作为类静态方法的生成器函数
  class Bar {
    static * generatorFn() {}
  }
  • 注意:箭头函数不能用来定义生成器函数

  • 标识生成器函数的星号不受两侧空格的影响

  • 调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行suspended的状态。

  • 与迭代器一致,生成器对象也实现了Iterator接口,因此具有next()方法。调用这个方法会让生成器开始或恢复执行。

  function * generatorFn () {}
  const g = generatorFn();

  console.log(g); // generatorFn {<suspended>}
  console.log(g.next); // f next() { [native code]}
  • next()方法的返回值类似迭代器,{done: false, value: }

    • value属性是生成器函数的返回值,默认值为undefined,可以通过生成器函数的返回值指定:
      function * generatorFn() {
        return 'foo';
      }
      let generatorObject = generatorFn();
      generatorObject.next(); // {done: true, value: 'foo'}
    
    
  • 生成器函数只会在初次调用next()方法后执行:

  function * generatorFn() {
    console.log('foobar')
  }
  // 初次调用生成器函数并不会打印日志
  let generatorObject = generatorFn();

  // 只有调用next()方法之后才会打印日志
  generatorObject.next(); // foobar
  • 生成器对象实现了Iterable接口,它们漠然的迭代器是自引用的:
  function * generatorFn() {}

  console.log(generatorFn); // f* generatorFn
  console.log(generatorFn()[Symbol.iterator]);
  // f [Symbol.iterator]() {native code}

  console.log(generatorFn());
  // generatorFn {<suspended>}

  console.log(generatorFn()[symbol.iterator]());
  // generatorFn {<suspended>}

  const g = generatorFn();
  g === g[Symbol.iterator]() // true

通过yield中断执行

  • yield关键字可以让生成器停止和开始执行,也是生成器最有用的地方。

    • 生成器函数在遇到yield关键字之前会正常执行。
    • 遇到这个关键字后,执行会停止,函数作用域的状态会被保留。停止执行的生成器函数只能通过在生成器对象上调用next()方法来恢复执行。
      function * generatorFn() {
        yield;
      }
    
      let generatorObject = generatorFn();
      
      generatorObject.next(); // {done: false, value: undefined}
      generatorObject.next(); // {done: true, value: undefined}
    
    • 此时的yield关键字有点像函数的中间返回语句,它生成的值会出现在next()方法返回的对象里。
    • 通过yield关键字退出的生成器函数会处在done: false状态;
    • 通过return关键字退出的生成器函数会处于done: true状态.
      function * generatorFn() {
        yield 'foo';
        yield 'bar';
        return 'baz';
      }
    
      let generatorObejct = generatorFn();
    
      generatorObject.next(); // { done: false, value: 'foo'};
      console.log(generatorObject.next());  // { done: false, value: 'bar' } 
      console.log(generatorObject.next());  // { done: true, value: 'baz' }
    
    • 生成器函数内部的执行流程会针对每个生成器对象区分作用域。在一个生成器对象上调用next()不会影响其他生成器:
      function * generatorFn () {
        yield 'foo';
        yield 'bar';
        return 'baz';
      }
    
      let genObj1 = generatorFn();
      let genObj2 = generatorFn();
      genObj1.next(); // {done: false, vlaue: 'foo'}
      genObj2.next(); // {done: false, vlaue: 'foo'}
    
    • yield关键字只能在生成器函数内部使用,在其他地方使用会报错。
    • yield关键字必须直接位于生成器函数定义中,出现在嵌套的非生成器函数中会抛出语法错误。
  1. 生成器对象作为可迭代对象

    • 在生成器对象上显式调用next()方法的用处并不大。其实,如果把生成器对象当成可迭代对象,那么使用起来会更方便:
      function * genFn() {
        yield 1;
        yield 2;
        yield 3;
      }
    
      for (const x of genFn()) {
        console.log(x);
      }
      // 1
      // 2
      // 3
    
    • 需要自定义迭代对象时,这样使用生成器对象特别有用。
  2. 使用yield实现输入和输出

    • 除了可以作为函数的中间返回语句使用,yield关键字还可以作为函数的中间参数使用。
    • 上次让生成器函数暂停的yield关键字会被接收到传给next()方法的第一个值。
    • 第一次调用next()传入的值不会被使用,因为这一次调用是为了开始执行生成器函数
      function * generatorFn(initial) {
        console.log(initial);
        console.log(yield);
        console.log(yield);
      }
    
      let generatorObject = generatorFn('foo');
      generatorObject.next('bar');  // foo 
      generatorObject.next('baz');  // baz 
      generatorObject.next('qux');  // qux 
    
    • yield关键字可以同时用于输入和输出
      function * generatorFn() {
        return yield 'foo';
      }
    
      let generatorObj = generatorFn();
    
      generatorObj.next(); // {done: false, value: 'foo'}
      generatorObj.next('obj'); // {done: true, value: 'bar}
    
    • 因为函数必须对整个表达式求值才能确定要返回的值,所以它在遇到yield关键字时暂停执行并计算出要产生的值:“foo”。下一次调用next()传入了"bar",作为交给同一个yield的值。然后这个值被确定为本次生成器函数要返回的值.

    • yield关键字并非只能使用一次。

  3. 产生可迭代对象

    • 可以使用星号增强yield的行为,让它能够迭代一个可迭代对象,从而产出一个值:
      // 等价的generatorFn:
      function * generatorFn() {
        for (const i of [1,2,3]) {
          yield x;
        }
      }
    
      // 等价于上
      function * generatorFn() {
        yield* [1,2,3]
      }
    
      let generatorObj = generatorFn();
      for (const x of generatorObj) {
        console.log(x);
      }
      // 1
      // 2
      // 3
    
    • yield 星号两侧空格不影响其行为。
    • 因为yield*实际上只是将一个可迭代对象序列化为一连串可以单独产出的值,所以这跟把yield放到一个循环里没什么不同。
    • yield*的值是关联迭代器返回done: true时的value属性。对于普通迭代器来说,这个值是undefined。
      function * gen() {
        console.log('iter value:', yield* [1,2,3]);
      }
    
      for(const x of gennn()) {
        console.log('value: ', x);
      }
      // value: 1
      // value: 2
      // value: 3
      // iter value: undefined
    
    • 对于生成器函数产生的迭代器来说,这个值就是生成器函数返回的值:
      function * innerGen() {
        yield 'foo';
        return 'bar';
      }
    
      function * outerGen(genObj) {
        console.log('iter value: ', yield * innerGen());
        // 所以 yield * innerGen()返回的值就是innerGen()函数返回的值'bar'
      }
    
      for (const x of outerGen()) {
        console.log('value: ', x);
      }
      // value: foo
      // iter value: bar
    
      // 因此遍历outerGen()会先执行yield 'foo',打印foo,然后打印bar
    
  4. 使用yield*实现递归算法

    • yield*最有用的地方就是实现递归操作,此时生成器可以产生自身:
      function * nTimes(n) {
        if (n > 0) {
          yield* nTime(n-1);
          yield n-1;
        }
      }
      for (const x of nTimes(3)) {
        console.log(x);
      }
      // 0
      // 1
      // 2
    
    • 在这个例子中,每个生成器首先都会从新创建的生成器对象产出每个值,然后再产出一个整数。结果就是生成器函数会递归地减少计数器值,并实例化另一个生成器对象。从最顶层来看,这就相当于创建一个可迭代对象并返回递增的整数.

    • 图的递归遍历,要看!!!

提前终止生成器

  • 与迭代器类似,生成器也支持“可关闭”的概念。一个实现Iterator接口的对象一定有next()方法,还有一个可选的return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法:throw()。
  function * generatorFn() {
    const g = generatorFn();

    console.log(g); // generatorFn {<suspended>}
    console.log(g.next); // f next() {native code}
    console.log(g.return); // f return() {native code}
    console.log(g.throw); // f throw() {native code}

  }
  • return()和throw()方法可以用于强制生成器进入关闭状态。
  1. return()

    • return()方法会强制生成器进入关闭状态。提供给return()方法的值,就是终止迭代器对象的值.
    • 与迭代器不同,所有生成器对象都有return()方法,只要通过它进入关闭状态,就无法恢复了。后续调用next()会显示done: true状态,而提供的任何返回值都不会被存储或传播.
    • for-of循环等内置语言结构会忽略状态为done: true的IteratorObject内部返回的值。
  2. throw()

    • throw()方法会在暂停的时候将一个提供的错误注入到生成器对象中。如果错误未被处理,生成器就会关闭。

    • 不过,假如生成器函数内部处理了这个错误,那么生成器就不会关闭,而且还可以恢复执行。错误处理会跳过对应的yield,因此在这个例子中会跳过一个值。

    • 如果生成器对象还没有开始执行,那么调用throw()抛出的错误不会在函数内部被捕获,因为这相当于在函数块外部抛出了错误。

【问】ES的错误捕获处理机制?

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值