• Generator函数(三)


    Generator.prototype.return()

    1.Generator函数返回的遍历器对象还有一个return方法,可以返回给定的值,并终结Generator函数的遍历。

        function* gen(){
            yield 1;
            yield 2;
            yield 3;
        }
        var g = gen();
        g.next();// {value:1,done:false}
        g.return("foo");//{value:foo,done:false}
        g.next();//{value:undefined,done:false}
        //上面的代码中,遍历器对象g调用return方法之后,返回的也是一个对象,而且这个对象的value属性就是return方法的参数foo.同时,Generator函数的遍历终止,返回值的done属性为true,以后再调用next方法,done属性总是返回true。如果return方法调用时不提供参数,则返回值的value属性为undefined。
    

    2.如果Generator函数内部有try...finally代码块,那么return方法会推迟到finally代码块执行完再执行。

    function* numbers(){
           yield 1;
           try{
                yield 2;
                yield 3;
            }finally{
                yield 4;
                yield 5;
            }
            yield 6;
    }
    var g = numbers();
    g.next(); //value:1
    g.next();//value:2
    g.return(7);//value:4
    g.next();//value:5
    g.next();//value:7
    

    yield*语句

    如果在Generator函数内部调用另一个Generator函数,默认情况下是没有效果的,必须要使用yield*语句
    1.这个语句的作用:主要是用来在一个Generator函数中使用另一个Generator函数。

        function* foo(){
            yield 'a';
            yield 'b';
        }
        function* bar(){
            yield 'x';
            foo();  // 将这里修改成yield* foo(),就会有效果
            yield 'y';
        }
        for(let v of bar()){
            console.log(v);
        }
        //"x"
        //"y"在这里面直接用yield语句是没有任何效果的
    

    2.从语法角度看,如果yield命令后面跟的是一个遍历器对象,那么需要在yield命令后面加上星号,表明返回的是一个遍历器对象。这被称为yield*语句。

        let delegatedIterator = (function* (){
            yield 'hello!';
            yield 'bye!';
        }());
    
        let delegatingIterator = (function* (){
            yield 'Greetings!';
            yield* delegatedIterator;
            yield 'ok,bye.';
        }())
    
        for(let value of delegatingIterator){
            console.log(value);
        }
    
        //"Greeting"
        //"hello"
        //"bye!"
        //"ok,bye"
    

    总结下来:(1)运行结果,就是使用一个遍历器遍历了多个Generator函数,有递归的效果。
    (2)yield* 语句等同于在Generator函数内部部署一个for...of循环。
    (3)yield* 语句后面还可以接数组,因为数组原生支持遍历器,因此会遍历数组成员
    (4)yield* 语句后面,只要是有Iterator接口,都可以用yield*遍历。

    3.如果被代理的Generator函数有return语句,那么可以向代理它的Generator函数返回数据。

        function* foo(){
            yield 2;
            yield 3
            return "foo";
        };
        function* bar(){
            yield 1;
            var v = yield* foo();
            console.log("v:" + v);
            yield 4;
        }
        var it = bar();
        it.next();//value:1
        it.next();//value:2
        it.next();//value:3
        it.next();//"v:foo",value:4这里不是太明白,姑且认为是因为foo函数被bar()函数代理的缘故
    

    4.yield*语句的一些比较常用的例子
    (1)可以很方便的取出嵌套数组的所有成员

        function* 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
    

    (2)yield* 语句可以用来遍历完全二叉树

        //用构造函数构造一个完全二叉树
        //3个参数分别是左子树,当前节点,右子树。
        function Tree(left,label,right){
            this.left = left;
            this.label = label;
            this.right = right;
        }
        
        //下面是中序(inorder)遍历函数
        //由于返回的是一个遍历器,所以要用Generator函数。
        //函数体内采用递归算法,所以左子树和右子树要用yield*遍历。
        function* inorder(t){
            if(t){
                yield* inorder(t.left);
                yield  t.label;
                yield* inorder(t.right);
            }
        }
    
        //下面生成二叉树
        function make(array){ //注意这个函数是一个普通函数
            //判断是否是叶子节点
            if(array.length == 1)   return new Tree(null,array[0],null);
            return new Tree(make(array[0]),array[1],make(array[2]));
        }
        
        let tree = make([[['a'],'b',['c']],'d',[['e'],'f',['g']]]);
        //var result = [];
        for(let node of inorder(tree)){
            //result.push(node);
            console.log(node);
        }
    

    作为对象属性的Generator函数

    1.如果一个对象的属性是一个Generator函数,则可以这样写:

        let obj = {
            * myGeneratorMethod(){
                ...
            }
        }
        //也可以这样写:
        let obj = {
            myGeneratorMethod:function* (){}
        };
    

    Generator函数的this

    1.Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,它也继承了Generator函数的prototype对象上的方法。
    2.如果只是把Generator函数当作普通的构造函数,并不会有任何效果,因为Generator函数返回的始终是遍历器对象,而不是this对象

    function* g(){
        this.a = 11;
    }
    let obj = g();
    obj.a //undefined
    

    3.还有如果使用new命令,这是不能生成F的实例,因为F返回的是一个内部指针。

    function* F(){
        yield this.x = 2;
        yield this.y = 3;
    }
    

    上面的方法是没有任何效果的,想要把Generator函数当作正常的构造函数来使用,可以采用下面的变通方法。

        function* F(){
            yield this.x = 2;
            yield this.y = 3;
        }
        var obj = {};
        var f = F.bind(obj)();//将obj和this绑定在一起。
        //再次调用next方法
        f.next();
        f.next();
        f.next();
        obj //{x:2,y:3}
    

    Generator函数推导

    1.利用函数推导,可以进行惰性求值,如果需要将一个数组中的每个成员进行平方,如果用到函数推导,那么就只会在用到的时候,才会占用系统资源。
    否则会先定义一个数组,这时候系统会被占用很大一部分资源。

        let generator = function* () {
            for(let i=0;i<6;i++){
                yield i;
            }
        }
        let squared = ( for (n of generator()) n*n);
        //等同于
        //let squared = Array.from(generator()).map( n => n*n);
        console.log(...squared);
        //0 1 4 9 16 25
    

    Generator函数推导是对数组结构的一种模拟,其最大的优点就是惰性求值,即直到真正用到的时候才会求值,这样可以保证效率。

    含义

    1.Generator与状态机
    Generator函数很好的实现了两个或者两个状态以上的切换过程,而且是合作式的。

        var clock = function*(_){
            while(true){
                yield _;
                console.log('Tick');
                yield _;
                console.log('Tock');
            }
        }  
    

    2.Generator与协程
    传统的“子例程”采用堆栈式“后进先出”的执行方式,只有当调用的子函数完全执行完毕,才会结束执行父函数。协程与其不同,多个线程(单线程情况下即多个函数)可以并行执行,但只有一个线程(或函数)处于正在运行的状态,其他线程(或函数)都处于暂停态,线程(或函数)之间可以交换执行权。也就是说,一个线程(或函数)执行到一半,可以暂停执行,将执行权交给另一个线程(或函数),等到稍后收回执行权时再恢复执行。这种可以并行执行、交换执行权的线程(或函数),就称为协程。
    从实现上看,在内存中子例程只使用一个栈,而协程是同时存在多个栈,但只有一个栈是在运行态。也就是说,协程是以多占用内存为代价实现多任务的并行运行。
    3.协程与普通线程的差异
    普通的线程是抢占式的,到底哪个线程优先得到资源,必须由运行环境决定,但是协程是合作式的,执行权由协程自己分配。而且Generator函数是ES6对协程的实现,但属于不完全实现。如果将Generator函数当作协程,完全可以将多个需要协作的任务写成Generator函数,他们之间用yield语句交换控制权。

    Generator函数的主要应用

    1.异步操作的同步化表达
    例如:ajax操作,

    function* main(){
    	var result = yield request("http:fff.url");//这里通过调用next方法来传递给result
    	var resp = JSON.parse(result);
    	console.log(resp.value);
    }
    
    function request(url){
    	makeAjaxCall(url,function(response){
    		it.next(response);//将这个response传递给result,这里必须在next方法加上response参数,因为yield语句构成的表达式本身是没有值的,总是等于undefined。
    	})//这个函数主要是用来获取应答数据
    }
    var it = main();
    it.next();
    

    2.控制流管理:同步运行和异步运行
    3.部署Iterator接口:利用Generator函数可以给任意对象部署Iterator接口,

    function* iterEntries(obj){
    	let keys = object.keys(obj);
    	for(let i=0;i<keys.length;i++){
    		let key = keys[i];
    		yield [key,obj[key];//这里通过yield
    	}
    }
    let myObj = {foo:3,bar:7};
    for(let [key,value] of iterEntries(myObj)){
    	console.log(key,value);//这里用for...of循环来遍历Generator函数
    }
    

    最后注意:
    1.yield 语句后面可以接普通函数,也可以正常执行。
    2.yield* 语句主要是用来在一个Generator函数中执行另一个Generator函数。主要可以实现递归。

  • 相关阅读:
    MYSQL视图的学习笔记
    MYSQL常用操作函数的封装
    table表格边框样式
    用于防SQL注入的几个函数
    Html中版权符号的字体选择问题(如何让版权符号更美观)
    拿出“请勿打扰”的态度来
    editplus批量删除html代码空行
    解决&nbsp在IE与firefox宽度不一致的问题
    解决IE6下DIV无法实现1px高度问题
    处理落后员工
  • 原文地址:https://www.cnblogs.com/sminocence/p/7277183.html
Copyright © 2020-2023  润新知