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函数。主要可以实现递归。

posted @ 2017-08-03 13:30  sminocence  阅读(193)  评论(0编辑  收藏  举报