ES6 Generator函数(1)

Generator 函数(1)

1.Generator函数的概述

    Generator函数是ES6提供的一种一部编程解决方案,语法行为与传统函数完全不同。对于Generator函数有多种理解角度。从语法上看,首先可以将它理解为一个状态机,封装了多个内部状态。执行Generator函数返回一个遍历器对象。
    形式上,Generator函数是一个普通函数,但是有两个特征:一是function命令与函数名之间有一个星号;二十函数体内部使用yield语句定义不同的内部状态。(yield => 产出)
1.1第一次使用Generator函数
 {
        function* helloWolrdGenerator(){
            yield 'hello';
            yield 'world';
            return 'ending';
        }

        let hw = helloWolrdGenerator();

        6(hw.next());
        //{value: "hello", done: false}
        console.log(hw.next());
        //{value: "world", done: false}
        console.log(hw.next());
        //{value: "ending", done: true}
        console.log(hw.next());
        // {value: undefined, done: true}
}

解析:上面代码定义了一个Generator函数,他的内部有两个yield语句,即该函数有三个状态:hello,world,return语句(结束执行)

调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是iterator对象。必须调用遍历器的next方法,使指针移动到下一个状态。换言之,Generator是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。

next 返回的value属性和done,value为值,done为判断遍历是否结束。
1.2yield表达式

由于Generator函数返回的遍历器对象只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。
yield语句就是暂停标志。

遍历器对象的next方法的运行逻辑如下:
1.遇到yield语句就暂停执行后面的操作,并将紧跟在后面的yield后的表达式的值作为返回的对象的value属性值。
2.下一次调用next方法再继续往下执行,知道遇到吓一跳yield语句。
3.如果没有再遇到新的yield语句,就一直运行到函数结束,知道return语句为止。
4.如果该函数没有return语句,则返回对象的value属性值undefined。

     {
        //yield表达式时惰性求值的
        function* add(){
            yield 123 + 456;
        }
        // 上述的yield表达式不会立即求值,只有当next方法将指针移到这个位置才求值。
    }

    {
        //yield表达式不能再其他函数内部使用
        function sum(a,b){
            yield(a+b);
        }
        sum(1,2);
        //yield is not defined

        (function(){
            yield 1;
        })()
        //Unexpected number
    }
1.3与iterator接口的关系

由于Generator函数就是遍历器生成函数,因此可以将Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有iterator接口。

     {
        let myIterator = {};
        myIterator[Symbol.iterator] = function* (){
            yield 1;
            yield 2;
            return 3;
        };
        console.log([...myIterator]);
        //[1,2]
    }
    // 上面的代码中,Generator函数赋值给Symbol.iterator属性,从而使得myIterator对象具有了iterator接口,
    // 可以被扩展运算符遍历。


    //
    {
        function* gen(){

        }

        let g = gen();
        console.log(g[Symbol.iterator]() === g);
        // /true
    }
    //上面的代码中,gen是一个Generator函数,调用会生成一个遍历器对象g。他的Symbol.iterator
    // 属性也是一个遍历器对象生成函数,执行后返回他自己。

2.next方法的参数

yield语句本身没有返回值,或者说是返回undefined。next方法可以带有一个参数,该参数会被当作上一条yield语句的返回值。
{
function* f(){
for(let i = 0 ; true; i ++){
let reset = yield i;

                if(reset){
                    i = -1;
                }
                console.log(reset);
            }
        }

        let g = f();
        console.log(g.next());  //0
        console.log(g.next());  //1
        console.log(g.next(true));  //0
        console.log(g.next());//1
        console.log(g.next());//2
    }

    上面的代码先定义了一个可以无限运行的Generator函数f,如果next方法没有参数,每次运行带yield语句时,变量reset的值
    总是undefined。当next方法带有一个参数true时,当前的变量reset就被重置为这个参数true,这时i=-1,下一圈开始从-1递增。
    只修改一次状态


    这个功能具有很重要的语法意义。Generator函数从暂停状态到恢复状态,上下文都是不变的。通过next方法的参数就有办法在Generator
    函数开始运行后继续向函数体内部注入值。也就是说,可以在Generator函数运行的不同阶段从外部向内部注入不同的值。
    {
        //不传入参数
        function* foo(x){
            let y = 2 * (yield (x + 1));
            let z = yield (y / 3);
            return (x + y + z);
        }

        let a = foo(5);
        a.next();  
        // {value:6,done:false}第一次返回x+1 为6  
        a.next();  
        // {value:NaN,done:false}
        // 因为这时y的值为 2 * undefined 为 NaN  所以NaN/3 = NaN
        a.next();
        //{value:NaN,done:true}
        //因为return的为 5 + NaN +undefined  所以为NaN

    }

    {
        //传入参数
        function* foo(x){
            let y = 2 * (yield (x + 1));  
            let z = yield (y / 3);
            return (x + y + z);
        }

        let a = foo(5);
        console.log(a.next());
        // {value:6,done:false}第一次返回x+1 为6  
        console.log(a.next(6));
        // {value:4,done:false}
        // 因为这时y的值为 12   所以value = 4
        console.log(a.next(8));
        //{value:33,done:true}
        //因为z=8 y = 12 x = 5  所以为25
    }


    因为next方法的参数表示的上一条yield语句的返回值,所以第一次使用next方法,V8引擎默认无效
    可以自己封装函数来完成第一次传递参数 
    参考<ES6标准入门> p324

3.for…of循环

for...of循环可以自动遍历Generator函数生成的Iterator对象,且此时不再需要调用next()方法。
for...of  循环
    {
        function* foo(){
        yield 1;
        yield 2;
        yield 3;
        yield 4;
        yield 5;
        return 6;
    }
    for(let k of foo()){
        console.log(k);
        //1 2 3 4 5
    }
    }

    一旦next方法返回对象的done属性为true,for...of循环就会停止,不包括return对象。
    

    {
        //Generator实现斐波那契
        function* fibonacci(){
            let [prev, curr] = [0,1];
            for(;;){
                [prev,curr] = [curr, prev + curr ];
                yield curr;
            }
        }

        for(let n of fibonacci()){
            if(n > 10)  break;
            console.log(n)
            //1 2 3 5 8     
        }
    }


    原生对象没有遍历接口,通过generator函数为他加上这个接口就可以使用for...of循环了
    {
        function* objcetEntires(obj){
            let propKeys = Reflect.ownKeys(obj);
            //Reflect.ownKeys 用于返回对象的所有属性

            for(let propKey of propKeys){
                yield [propKey,obj[propKey]]
            }
        }

        let jane = {
            first:'jane',
            second:'Doe'
        }

        for(let [key,value] of objcetEntires(jane)){
            console.log(key + ':' + value);
            //first:jane
            //second:Doe
        }
    }


    {
        // 第二种写法
        function* objcetEntires(){
            let propKeys = Object.keys(this);

            for(let propKey of propKeys){
                yield [propKey,this[propKey]];
            }
        }

        let jane = {
            first:'jane',
            second:'Doe'
        }

        jane[Symbol.iterator] = objcetEntires;
        for(let [key,value] of jane){
            console.log(key + ':' + value);
            //first:jane
            //second:Doe
        }

    }

    除了for...of循环,扩展运算符(...),解构赋值和Array.from方法内部调用的都是遍历器接口,这意味着
    他们都可以将Generator函数返回的Iterator对象作为参数。

4.Generator.prototype.throw()

 Generator函数返回的遍历器对象都有一个throw方法,可以在函数体外抛出错误,然后再Generator函数体内捕获
    {
        let g = function* (){
            try{
                yield;
            } catch(e) {
                console.log('内部捕获',e);
            }
        }

        let i = g();
        console.log(i.next());

        try{
            i.throw('a');
            i.throw('b');
        } catch(e) {
            console.log('外部捕获',e);
        }

        //内部捕获 a
        //外部捕获 b
    }

    上面的代码中,遍历器对象i连续抛出两个错误,第一个错误被Generator 函数体内的catch语句捕获。i第二次抛出错误,
    由于Generator函数内部的catch语句已经执行过了,不会在捕捉到这个错误了,所以这个错误被抛出了Generator函数体,被函数体外的catch语句捕获。
    
    {
        let g = function* (){
            try{
                yield;
            } catch(e) {
                console.log('内部捕获',e);
            }
        }

        let i = g();
        console.log(i.next());

        try{
            i.throw('a');
            i.throw('b');
        } catch(e) {
            console.log('外部捕获',e);
        }

        // 外部捕获 a
    }

    如果不执行Generator函数,则外部会捕获a这个错误


    {
        // 如果Generator函数内部没有部署try...catch代码块,那么throw方法抛出的错误将被外部try...catch代码块捕获。
        let g = function* (){
            while (true) {
                yield;
                console.log('内部捕获',e);
            }
        };

        let i = g();

        i.next();

        try {
            i.throw('a');
            i.throw('b');
        } catch(e) {
            console.log('外部捕获',e);
        }
        //外部捕获 a
    }

    如果generator函数内部部署了try…catch语句,那么遍历器的throw方法抛出的错误不影响下一次遍历;
    否则导致程序报错;中断执行;throw方法被捕获以后会附带执行下一条yield命令;

    {
        let g = function* (){
            try{
                yield ('a')
            } catch(e){
                yield (e);
            }
            yield ('c');
            yield ('d');
        }

        let i = g();
        console.log(i.next());
        //{value: "a", done: false}
        console.log(i.throw('b'));
        //{value: "b", done: false}
        console.log(i.next());
        //{value: "c", done: false}
        console.log(i.next());
        // {value: "d", done: false}
    }


    一旦Generator函数运行中出现错误,就不会继续执行下去了,js引擎认为这个Generator已经运行结束。
    {
        let g = function* (){
            yield ('a');
            yield ('b');
            throw new Error('Generator an exception');
            yield ('c');
        }

        let i = g();

        //第一次运行
        try{
            console.log(i.next());
        } catch(err){
            console.log(err);
        }
        //{value: "a", done: false}

        //第二次运行
        try{
            console.log(i.next());
        } catch(err){
            console.log(err);
        }
        //{value: "b", done: false}
        //第三次运行
        try{
            console.log(i.next());
        } catch(err){
            console.log(err);
        }
        // Generator an exception

        //第四次运行
        try{
            console.log(i.next());
        } catch(err){
            console.log(err);
        }
        //{value: "undefined", done: true}
    }

    当Generator函数执行过程中抛出错误,就不会继续执行下去了。如果此后调用next方法,将返回一个value属性为undefined
    done属性为true的对象,js引擎默认认为这个Generator已经运行结束。

5.Generator.prototype.return()

    Generator函数返回的遍历器对象还有一个return方法,可以返回给定的值,并中介Generator函数的遍历。
    {
        function* gen(){
            yield 1;
            yield 2;
            yield 3;
            return 4;
            yield 5;
        }

        let g = gen();
        console.log(g.next());
        //{value: 1, done: false}
        console.log(g.next());
        //{value: 2, done: false}
        console.log(g.next());
        //{value: 3, done: false}
        console.log(g.next());
        // {value: 4, done: true}
        console.log(g.next());
        // {value: undefined, done: true}
    }

6.Generator函数this

Generator函数总是返回一个遍历器,ES6规定这个遍历器时Generator函数的实例,他也继承了Generator函数的prototype对象上的方法。
 {
        function* g(){};

        g.prototype.name = function(){
            return 'kjh'
        }

        let obj = g();

        console.log(obj instanceof g);
        //true
        console.log(obj.name());
        //kjh
    }

    上面代码表明,Generator函数g返回的遍历器obj时g的实例并且继承了原型上的方法。

    {
        function* g(){
            this.a = 11;
        }

        let obj = g();
        console.log(obj.a);
        //undefined
    }

    如果将g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
    也不能调用new因为他不是构造函数。

    返回一个正常的实例,既可以调用next方法,又可以获得正常的this
    {
        function* f(){
            this.a = 1;
            yield this.b = 2;
            yield this.c = 3;
        }
        let obj = {};

        let f1 = f.call(obj);
        console.log(f1.next());  
        //{value:2,done:false}
        console.log(f1.next());
        //{value:3,done:false}
        console.log(f1.next());

        console.log(obj.a); //1
    }

7.Generator 与 状态机

Generator时实现状态机的最佳结构。
 {
        let ticking = true;
        let clock = function(){
            if(ticking){
                console.log('Tick!');
            }else{
                console.log('tock!'); 
            }
            ticking = !ticking;
        }
        clock();
        clock();
    }

    {
        //使用Generator函数实现没运行一次状态修改一次
        let clock = function* (){
            while(true){
                console.log('tick');
                yield;
                console.log('tock');
                yield;
            }
        }
    }

参考文献:<es6标准入门> 阮一峰著

发布了85 篇原创文章 · 获赞 16 · 访问量 6109

猜你喜欢

转载自blog.csdn.net/qq_43955202/article/details/104333544