生成器

104 阅读3分钟

生成器基础

生成器是 ECMAScript 6 新增的一个极为灵活的结构,拥有在一个函数块内暂停和恢复代码执行的 能力。这种新能力具有深远的影响。

函数名称前面加一个星号(*)表示它是一个生成器。只要是可以定义函数的地方,就可以定义生成器。

// 生成器函数声明
function* generatorFn() {}

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


// 作为类实例方法的生成器函数
class Foo {
 *generatorFn() {}
}

调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器开始或恢复执行。

我们可以来复习一下什么是Iterator 接口:暴露一个属性Symbol.iterator作为默认迭代器。值为一个迭代器工厂函数,调用它返回一个新迭代器,也就一个对象。

生成器函数只会在初次调用 next()方法后开始执行

 function* generatorFn() {}
 const g = generatorFn();
 console.log(g); // generatorFn {<suspended>}
 console.log(g.next); // f next() { [native code] }

通过 yield 中断执行

yield 关键字可以让生成器停止和开始执行。遇到这个关键字后,执行会停止,函数作用域的状态会被保留。再次开启需要调用next方法。并且我们在上文说过,生成器函数只会在初次调用 next()方法后开始执行。所以当我们第一次调用next方法时,就会执行到第一个yield关键字所在的地方。

 function* generatorFn() {
    yield 'foo';
    yield 'bar';
    return 'baz';
 }
 let generatorObject = generatorFn();
 console.log(generatorObject.next()); // { done: false, value: 'foo' }
 console.log(generatorObject.next()); // { done: false, value: 'bar' }
 console.log(generatorObject.next()); // { done: true, value: 'baz' }

此时的yield 关键字有点像函数的中间返回语句,它生成的值会出现在 next()方法返回的对象里。通过 yield 关键字退出的生成器函数会处在 done: false 状态;通过 return 关键字退出的生成器函数会处于 done: true 状态。

在生成器对象上显式调用 next()方法的用处并不大。其实,如果把生成器对象当成可迭代对象,那么使用起来会更方便:

  function* generatorFn() {
    yield 1;
    yield 2;
    yield 3;
  }
  for (const x of generatorFn()) {
    console.log(x);
  }
  // 1
  // 2
  // 3

yield 关键字还可以作为函数的中间参数使用:

  function* generatorFn() {
    console.log(yield);
    console.log(yield);
    console.log(yield);
  }
  let generatorObject = generatorFn();
  generatorObject.next("bar"); //注意,第一个next启动生成器,所以不执行
  generatorObject.next("baz"); // baz 等于说把next的参数当成了yield
  generatorObject.next("qux"); // qux

生成器作为默认迭代器

在上一篇文章我们讲过默认的迭代器方法是`Symbol.iterator这样的。又因为生成器对象实现了 Iterable 接口,而且生成器函数和默认迭代器被调用之后都产生迭代器,所以生成器格外适合作为默认迭代器。

 class Foo {
   constructor() {
     this.values = [1, 2, 3];
   }

   *[Symbol.iterator]() {
     yield* this.values;
   }
 }
 const f = new Foo();
 for (const x of f) {
   console.log(x);
 }
 // 1
 // 2
 // 3

提前终止生成器

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

 function* generatorFn() {
   for (const x of [1, 2, 3]) {
     yield x;
   }
 }
 const g = generatorFn();
 console.log(g); // generatorFn {<suspended>}
 console.log(g.return(4)); // { done: true, value: 4 }
 console.log(g); // generatorFn {<closed>}

与迭代器不同,所有生成器对象都有 return()方法,只要通过它进入关闭状态,就无法恢复了。后续调用 next()会显示 done: true 状态。

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

   function* generatorFn() {
     for (const x of [1, 2, 3]) {
       yield x;
     }
   }
   const g = generatorFn();
   console.log(g); // generatorFn {<suspended>}
   try {
     g.throw("foo");
   } catch (e) {
     console.log(e); // foo
   }
   console.log(g); // generatorFn {<closed>}

END