Generator 函数初探

前言

Generator 函数是 ES6 一个很重要的特性,但其涉及的概念却不太能一眼就看懂,所以写下这篇小文以作学习记录。

定义一个 generator 函数

定义一个 generator 函数十分简单,和正常的函数定义相比,多加一个 * 就可以了:

function * name(arguments) {
  statements
}

但 generator 函数与普通函数的区别却非常大,它的优势在于,退出函数之后可以再次进入,函数的上下文会被保留。每次退出 generator 函数,它都会返回一个值,这不是一个普通的值,而是一个迭代器(iterator)对象。下面就来说一说迭代器对象。

迭代器对象

迭代器有一个特别棒的特性,就是可以记录当前数组遍历到的节点位置。除此之外,按照 协议规范,迭代器对象需要提供一个 next 方法继续遍历下一个属性值,该方法返回一个如下对象:

{
  done: <boolean>,  // 用于判断是否已经到数组最尾部
  value: <any>      // 当前位置的返回值
}

下面让我们实现一个简单的迭代器构造函数:

function Iterator(array){
  var position = 0;

  return {
    next: function () {
      if(position < array.length){
        return {
          done: false,
          value: array[position++]
        }
      }else {
        return {
          done: true
        };
      }
    }
  }
}

var iterableArray = new Iterator([1,2,3]);
console.log(iterableArray.next().value) // 1
console.log(iterableArray.next().value) // 2
console.log(iterableArray.next().value) // 3
console.log(iterableArray.next().done)  // true

一些内置的可迭代的对象有 StringArrayTypedArrayMapSet,简单的判断规则就是:可以使用 for...of 遍历的对象。这样的对象也可以使用 扩展运算符(…)解构赋值(destructuring assignment) 和 generator,generator 提供了更好的遍历可迭代对象的方法。

yield 关键字

现在我们知道了如何定义 generator 函数以及 generator 函数返回的是迭代器对象。那么就先来试着写一下,注意使用 node --harmony 执行:

function* gen() {
    return 'hello';
}
console.log(gen()); // {} - 返回一个迭代器对象
console.log(gen().next()); // { value: 'hello', done: true }

结果符合理论的,接下来我们来解决一个问题:如何在 generator 函数中多次返回 iterator 对象?

为了解决这个问题,ES6 引入了一个关键字 —— yield ,其用处是暂停和继续 generator 函数,并返回一个迭代器对象。再来试一把:

function* gen(name) {
    yield 'Hello, ' + name;
    return 'Bye, ' + name;
}
console.log(gen('Alice').next()); // { value: 'Hello, Alice', done: false }
console.log(gen('Bob').next()); // { value: 'Hello, Bob', done: false }

References