八、ES6系列之Generator

前面我们介绍过 Iterator(迭代器),本文主要了解 Generator (生成器)函数。
生成器是一种返回迭代器的函数,通过 function 关键字后的星号(*)来表示,函数中会用到新的关键字 yield。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function* createIterator(item) {
for (let i = 0; i < item.length; i++) {
yield item[i];
}
}

var it = createIterator([10, 2, 4, 5, 6]); // 生成器函数执行返回一个新的迭代器实例it
// 调用迭代器it的next()方法
console.log(it.next()); //{value: 10, done: false}
console.log(it.next()); //{value: 2, done: false}
console.log(it.next()); //{value: 4, done: false}
console.log(it.next()); //{value: 5, done: false}
console.log(it.next()); //{value: 6, done: false}
console.log(it.next()); //{value: undefined, done: true}

或者使用 for…of 方法统一遍历

1
2
3
for (var v of it) {
console.log(v); // 10 2 4 5 6
}

从上例可以看到,使用了 ES6 的生成器,明显简化迭代器的创建过程,给生成器函数 createIterator()传入一个 item 数组,函数内部,for 循环不断从数组中生成新的元素放入迭代器中,每遇到一个 yield 语句循环都会停止;每次调用迭代器的 next()方法,循环便继续运行并停止在下一条 yield 语句处。

生成器函数 createIterator()执行后创建的迭代器赋值给变量 it,变量 it 就作为这个迭代器的引用。既可以通过手工调用 next()方法来执行迭代过程,也可以使用 for..of..来完成迭代过程。

生成器的种类

一般分类四种:

  1. 生成器函数声明:
1
2
function* genFunc() { ··· }
const genObj = genFunc();
  1. 生成器函数表达式:
1
2
const genFunc = function* () { ··· };
const genObj = genFunc();
  1. 对象字面量中的生成器方法定义:
1
2
3
4
5
6
const obj = {
* generatorMethod() {
···
}
};
const genObj = obj.generatorMethod();
  1. 类定义中的生成器方法定义(类声明或类表达式):
1
2
3
4
5
6
7
class MyClass {
* generatorMethod() {
···
}
}
const myInst = new MyClass();
const genObj = myInst.generatorMethod();

next 方法的传参

在生成器函数内部使用 yield 关键字暂停,yield 表达式本身没有返回值,或者说总是返回 undefined。在该函数执行返回的迭代器上调用 next()获得暂停时的返回值。其实 next()方法可以接收参数,这个参数的值会代替生成器内部上一条 yield 语句的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 生成器
function* createIterator() {
var first = yield 2;
var second = yield first * 3;
yield second + 3;
}

// 创建迭代器实例
var it = createIterator();

// 启动迭代器
it.next(); // {value: 2, done: false}
it.next(4); // {value: 12, done: false}
it.next(7); // {value: 10, done: false}
it.next(); // {value: undefined, done: true}

这个功能有很重要的语法意义。Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过 next 方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

Generator.prototype.throw()

throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var g = function* () {
try {
yield;
} catch (e) {
console.log("内部捕获", e);
}
};

var i = g();
i.next();

try {
i.throw(new Error("a"));
i.throw(new Error("b"));
} catch (e) {
console.log("外部捕获", e);
}
// 内部捕获 Error: a
// 外部捕获 Error: b

上面代码中,遍历器对象 i 连续抛出两个错误。第一个错误被 Generator 函数体内的 catch 语句捕获。i 第二次抛出错误,由于 Generator 函数内部的 catch 语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的 catch 语句捕获

Generator.prototype.return()

return()方法,可以返回给定的值,并且终结遍历 Generator 函数。

1
2
3
4
5
6
7
8
9
10
function* createIterator(item) {
for (let i = 0; i < item.length; i++) {
yield item[i];
}
}

var it = createIterator([10, 2, 4, 5, 6]);
console.log(it.next()); //{value: 10, done: false}
console.log(it.return("over!")); //{value: 'over!', done: true}
console.log(it.next()); //{value: undefined, done: true}

上面代码中,遍历器对象 it 调用 return()方法后,返回值的 value 属性就是 return()方法的参数。并且,Generator 函数的遍历就终止了,返回值的 done 属性为 true,以后再调用 next()方法,done 属性总是返回 true。

总结

最后总结下,生成器是创建迭代器的函数,生成器函数内部有 yield 关键字来提供暂停接口,作为创建的迭代器调用 next()方法执行的节点。生成器函数与普通函数的区别是前者在 function 关键字后有星号(*),并且生成器函数执行后会创建一个新的迭代器实例,其他则和普通函数一样,可以传参和返回值。迭代器的 next()方法可以传入参数,传入的参数值将会代替迭代器内上一条 yield 语句的返回值。

参考文献