十分钟带你解读Generator函数的基本语法

简介

基本概念

    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方法的运行逻辑如下:

  1. 遇到yield语句就暂停执行后面的操作,并将紧跟在yield后的表达式的值作为返回的对象的value属性值。
  2. 下次调用next方法时再继续往下执行,知道遇到下一条yield语句。
  3. 如果没有遇到新的yield语句,就一直运行到函数结束,知道return语句为止,并将return语句后面的表达式的值作为返回对象的value属性值。
  4. 如果函数没有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

 

 

发布了64 篇原创文章 · 获赞 45 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/DengZY926/article/details/84308584