调整字号

ES6 快速上手:生成器

es6-small

生成器又叫“Generators”,是 ES6 的一个重要的新特性。

生成器是一种特殊的函数,下面就是一个生成器:

function* demo(name) {
  yield "Hi " + name + "!";
  yield "Welcome!";
  yield "Goodbye!";
}

调用以后,它返回一个迭代器。里面的 yield 关键字,用来指定每次调用 .next() 所应该返回的值。调用一下:

var iterator = demo("William");

iterator.next(); //{ value: "Hi William!", done: false }
iterator.next(); //{ value: "Welcome!", done: false }
iterator.next(); //{ value: "Goodbye!", done: false }
iterator.next(); //{ value: undefined, done: true }

可以看到,生成器函数的一个特点,就是每次遇到 yield 关键字,函数的执行都会暂停,yield 后面的表达式的值被返回。再调用 .next() ,函数就往下执行,到下一个 yield

生成器也有表达式语法:

let myGenerator = function *(arg) {
  ...
};

生成器的语法要点就两部分:function*yield* 两边允许空格的存在,只要是在 function 和函数名之间就可以。 yield 后面可以跟任意的表达式。

生成器的主要用处是两个:作为迭代器,异步编程。

作为迭代器使用

生成器作为一种函数,它可以成为对象的属性。回想一下《for…of 和迭代器》中所讲的可遍历对象,只要对象拥有一个能返回迭代器的 [Symbol.iterator]() 方法,就能成为一个可遍历对象。既然生成器正好就返回一个迭代器,所以,生成器就是 [Symbol.iterator]() 方法的最佳选择。比如:

var o = {
  items: [1, 2, 3],
  [Symbol.iterator]: function *() {
    for(let i = 0; i < this.items.length; i++) {
      yield this.items[i];
    }
  }
};

用 ES6 新的简写语法,还可以这样写:

var o = {
  items: [1, 2, 3],
  *[Symbol.iterator]() {
    for(let i = 0; i < this.items.length; i++) {
      yield this.items[i];
    }
  }
};

生成器里也可以使用 return 来提前结束遍历:

function *foo() {
    yield 1;
    return;
    yield 2;
    yield 3;
}
let iterator = foo();

iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: undefined, done: true }

迭代器的 .next() 方法还可以传参,比如

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2; // 4 + 2
    yield second + 3; // 5 + 3
}

let iterator = createIterator();

iterator.next(); // { value: 1, done: false }
iterator.next(4); // { value: 6, done: false }"
iterator.next(5); // { value: 8, done: false }"
iterator.next(); // { value: undefined, done: true }

传给 .next() 的参数,会变为上一个 yield 的返回值。因此,第一次调用 .next() 时不要传参数,传了也获取不到。

解析一下上例的执行过程:第一次调用 .next() 时,生成器内部的代码开始执行。第一句是一个赋值语句 let first = yield 1; 自右向左计算,所以先 yield 1,暂停了。第二次调用 .next() 并传入了 4,函数继续执行左边部分 let first = ...,因为 4 成为 yield 的返回值,所以 let first = 4。第二行同理,在 yield first + 2 也就是 yield 6 暂停。第三次调用 .next(5)以后,let second = 5,最后 yield second + 3 的结果是 8。

生成器另一大用处是在异步编程中,因为生成器有暂停的功能。执行一个异步函数时,可以 yield 暂停,等到异步任务完成,再调用 .next() 继续。

yield*

生成器还可以委托,说白了就是,在生成器中调用另一个生成器,例如:

function *generator1() {
    yield 1;
    yield 2;
}
function *generator2() {
    yield "white";
    yield "black";
}
function *combine() {
    yield *generator1();
    yield *generator2();
    yield "end!";
}

var iterator = combine();

iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: "white", done: false }
iterator.next(); // { value: "black", done: false }
iterator.next(); // { value: "end!", done: false }
iterator.next(); // { value: undefined, done: true }

上例,先委托给了 generator1(),然后委托给了 generator2(),最后自己也 yield 了一个值。

生成器委托的语法是:yield** 的两边也允许空格,只要在 yield 和生成器名字之间就可以。

还没有评论,沙发空缺中……
flight