zoukankan      html  css  js  c++  java
  • 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函数。主要可以实现递归。

  • 相关阅读:
    Atitit 趋势管理之道 attilax著
    Atitit 循环处理的新特性 for...else...
    Atitit 2017年的技术趋势与未来的大技术趋势
    atitit 用什么样的维度看问题.docx 如何了解 看待xxx
    atitit prj mnrs 项目中的几种经理角色.docx
    Atitit IT办公场所以及度假村以及网点以及租房点建设之道 attilax总结
    Atitit 工具选型的因素与方法 attilax总结
    Atitit.团队文化建设影响组织的的一些原理 法则 定理 效应 p826.v4
    Atiitt 管理方面的误区总结 attilax总结
    Atitit 未来趋势把控的书籍 attilax总结 v3
  • 原文地址:https://www.cnblogs.com/sminocence/p/7277183.html
Copyright © 2011-2022 走看看