简介
基本概念
Generator函数是ES6提供的一种异步变成解决方案,语法行为与传统函数完全不同。
Generator在形式上是一个普通函数,但是有两个特征:一是function命令与函数名之间有一个星号;二是函数体内部使用yield语句定义不同的内部状态。
function* helloGenerator() {
yield ‘hello’;
yield ‘word’;
return ‘ending’;
}
var hw = helloGenerator();
上面的代码定义了一个Generator函数,他的内部有两个yield语句,即该函数有3个状态:hello、world和return语句。
调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。必须调用遍历器的next方法,使得指针指向下一个状态。简单的说,Generator函数是分段执行的yield语句是暂停执行的标记,而next方法可以恢复执行。
hw.next()
// { value: ‘hello’, done: false }
hw.next()
// { value: ‘word, done: false }
hw.next()
// { value: ‘ending, done: true}
hw.next()
// { value: undefined done: true}
从上面的代码我们可以看出,没调用一次next方法,就会执行到下一个最近的yield语句处停下来,并返回yield后面的值,其中返回值中的value就是返回的值得内容,done表示遍历是否结束,如果为false表示未结束,反之则为便利结束。当遍历结束后继续调用next方法,则永远返回的都是{ value: undefined done: true}。
yield表达式
由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。Yield语句就是暂停的标志。
遍历器对象的next方法的运行逻辑如下:
- 遇到yield语句就暂停执行后面的操作,并将紧跟在yield后的表达式的值作为返回的对象的value属性值。
- 下次调用next方法时再继续往下执行,知道遇到下一条yield语句。
- 如果没有遇到新的yield语句,就一直运行到函数结束,知道return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值。
- 如果函数没有return语句,则返回对象的value属性值为undefined。
yield表达式只能用在Generator函数里面,用在其他地方都会报错。
(function (){
yield 1;
})()
// SyntaxError: Unexpected number
yield表达式如果用在另一个表达式中,必须放在圆括号里面。
function* demo() {
Console.lo(‘hello’ + yield ); //SyntaxError
Console.lo(‘hello’ + yield 123 ); //SyntaxError
Console.lo(‘hello’ + (yield) ); //OK
Console.lo(‘hello’ + (yield 123) ); //OK
}
yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
foo( yield ‘a’, yield ‘b’ );
let input = yield;
}
与iterator接口的关系
由于Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有iterator接口。
var myIterable = { };
myIterable[ Symbol.iterator ] = function* () {
yield 1;
yield 2;
yield 3;
};
[ ...myIterable ] // [ 1, 2, 3 ]
next方法的参数
yield语句本身没有返回值,或者说总是返回undefined。next方法可以带有一个参数,该参数会被当做上一条yield语句的返回值。
function* demo(x) {
var y = 2 * ( yield ( x + 1 ) );
var z = yield ( y / 3 );
return ( x + y + z );
}
var a = demo(5);
a.next(); //Object{ value: 6, done: false}
a.next(); //Object{ value: NaN, done: false}
a.next(); //Object{ value: NaN, done: true}
var b = demo(5);
b.next(); //Object{ value: 6, done: false}
b.next(12); //Object{ value: 8, done: false}
b.next(13); //Object{ value: 42, done: true}
上面的代码第二次运行next方法的时候不带参数,导致y的值等于2 * undefined(即NaN)。如果next提供参数,则表示返回该参数。
for...of循环
for...of循环可以自动遍历Generator函数生成的iterator对象,且此时不在需要调用next方法。
function* demo() {
yield 1;
yield 2;
yield 3;
yield 4;
return 5;
}
for( let v of demo()){
Console.log(v);
}
// 1 2 3 4
注:一旦next方法的返回对象的done属性为true,for..of循环就会终止,且不包括该返回对象,所以上面的return语句返回的5不包括在for...of循环中。
应用案例:
利用Generator函数和for...of循环实现斐波那契数列。
function* fibonacci() {
let [prev, curr] = [ 0, 1];
for( ; ; ){
[ prev, curr] = [ curr, prev + curr];
yield curr;
}
}
for( let n of fibonacci()) {
if(n > 1000)
break;
console.log( n );
}
除了for...of循环,扩展运算符( ... )、解构赋值和Array.form方法内部调用的都是遍历器接口。着一位置,他们都可以将Generator函数返回的Iterator对象最为参数。
function* numbers () {
yield 1;
yield 2;
return 3;
yield 4;
}
//扩展运算符
[ ..numbers() ] //[ 1, 2 ]
//Array.form方法
Array.form(numbers() ) // [ 1, 2 ]
//解构函数
let [ x, y] = numbers();
x //1
y //2
//for ... of 循环
for ( let n of numbers() ) {
console.log(n);
}
// 1
// 2
Generator.prototype.throw()
Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后再Generator函数函数体内捕获
var g = function* (){
try {
yield;
}catch(e) {
console.log(‘内部捕获’, e);
}
};
var i = g();
i.next();
try {
i.throw(‘a’);
i.throw(‘b’);
}catch(e) {
console.log(‘外部捕获’,e);
}
//内部捕获 a
//外部捕获 b
第一次错误被Generator函数体内的catch捕获。i的第二次抛出错误,由于Generator函数内部的catch语句已经执行过了,不在捕获到这个错误,所以这个错误就抛出了Generator函数体,被函数体外的catch语句捕获。
如果Generator函数内部没有部署try...catch代码块,那么throw方法抛出的错误将被外部try...catch代码块捕获。
如果Generator函数内部部署了try...catch代码块,那么遍历的throw方法抛出的错误不影响下一次遍历,否则遍历直接终止。
var g = function* (){
while(true) {
yield;
console.log(‘内部捕获’, e);
}
};
var i = g();
i.next();
try {
i.throw(‘a’);
i.throw(‘b’);
}catch(e) {
console.log(‘外部捕获’,e);
}
//外部捕获 a
throw方法被捕获后会附带执行下一条yield表达式,即附带执行一次next方法。
var gen = function* () {
try [
yield console.log(‘a’);
} catch(e) {
//...
}
yield console.log(‘b’);
yield console.log(‘c’);
}
var g = gen();
g.next() //a
g.throw() //b
g.next() //c
throw命令与g.throw方法是无关的,两者互不影响。
Generator函数体外抛出的错误可以在函数体内捕获;反过来,Generator函数体内抛出的错误也可以被函数外的catch捕获。
Generator.prototype.return()
Generator函数返回的遍历器对象哈还有一个return方法,可以返回给定的值,并终结Generator函数的遍历。
function* demo() {
yield 1;
yield 2;
yield 3;
}
var g = demo();
g.next(); // { value: 1, done: false}
g.return(‘foo’); // { value: ‘foo’, done: true}
g.next(); // { value: undefined, done: true}
如果return方法调用时不提供参数,则返回值的value属性为undefined。
如果Generator函数体内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。
function* demo() {
yield 1;
try {
yield 2;
yield 3;
} finally {
yield 4;
yield 5;
}
yield 6;
}
var g = demo();
g.next(); // { value: 1, done: false}
g.next(); // { value: 2, done: false}
g.return(7); // { value: 4, done: false}
g.next(); // { value: 5, done: false}
g.next(); // { value: 7, done: true}
yield*表达式
如果在Generator函数内部调用另外一个Generator函数,默认情况下是没有效果的。
这时就需要用到yield*语句,用来在一个Generator函数里面执行另外一个Generator函数。
function* foo() {
yield ‘a’;
yield* foo();
yield ‘b’;
}
function* bar() {
yield ‘x’;
yield* foo();
yield ‘y’;
}
//等同于
function* bar() {
yield ‘x’;
yield ‘a’;
yield ‘b’;
yield ‘y’;
}
应用举例:
取一个嵌套数组的所有成员。f
unction* iterTree(tree) {
if (Array.isArray( tree )) {
for( let i = 0; i < tree.length ; i++ ){
yield* iterTree( tree.i );
}
} else {
yield tree;
}
}
const tree = [ ‘a’, [‘b’, ‘c’], [‘’d, ‘e’] ];
for(let x of iterTree(tree)){
console.log(x);
}
//a
//b
//c
//d
//e
作为对象属性的Generator函数
如果一个对象的属性是Generator函数,那么可以简写成下面的形式。l
et obj = {
* myGeneratorMethod() {
...
}
};
//等价于
let obj = {
myGeneratorMethod: function* () {
...
}
};
Generator函数this
Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,它也继承了Generator函数的prototype对象上的方法。
function* g() {
this.a = 11;
}
g.prototype.hello = function () {
return ‘hi’;
};
let obj = g();
obj instanceof g //true
obj.hello() // ‘hi’
obj.a //undefined
Generator函数也不能跟new命令一起用,否则会报错。
new g()
// TypeError : g is not a constructor