Generator生成器 —— yield与yield*表达式

前面的话

前面介绍过Generator函数是一个状态机,里面封装了多个内部状态,并且只有调用对象的next()方法才能移到下一个状态。而每一个状态都是由yield语句开头的(或者return语句开头)。即只有调用next方法且内部指针指向该语句时,才会执行yield语句后面的表达式。所以yield语句表示一个状态,而yield*语句作用又不一样了,它功能相当于for…of的功能。

yield表达式

yield语句与return语句的区别与联系:

  • 函数每次遇到yield语句就会暂停执行,下一次会从该位置继续向后执行;而return语句不具备记忆功能,执行到return语句函数执行结束
  • 函数里面可以执行多条yield语句,但只能执行一次return语句
  • yield语句与return语句都能返回紧跟在语句后面的表达式的值(yield语句本身没有返回值,只是把后面的表达式的赋给Generator对象调用next() 方法返回的对象的value属性)
  • yield语句只能在Generator函数中,不能再其他函数中;return函数可以存在任意函数中。
  • Generator函数可以不具有yield语句与return语句;不具有yield语句函数就变成了单纯的暂缓执行函数

举三个例子说明上面的情况:

   function* gen() {
       console.log('staring');
       yield 'b';
       yield 'c';
       yield 'd';
       return 'e';
       yield  'ending'
   }
   var x = gen();
    console.log( x.next());// staring  {value: b,done:false}
    console.log( x.next());// {value: c,done:false}
    console.log( x.next());// {value: d,done:false}
    console.log( x.next());// {value: e,done:true}
    console.log( x.next());// {value: undefined,done:true}

这个例子可以说明上面的前三种情况。在这里插入图片描述

    (function  foo() {
       yield 1;
   }())

在这里插入图片描述
在一个普通函数中使用yield语句,会报错。这一点说明上述的第4点,yield语句只能存在Generator函数中

   function* g() {
       console.log('x');
   }
   var ge = g();
   setTimeout(() => {
       ge.next();
   }, 2000);

如果上述是一个普通函数,那么在变量ge被赋值时,就会执行。但函数是一个Generator函数,于是只有调用next方法才会执行。这个例子说明上述第5点

[yield表达式使用规则]

yield表达式如果用在另一个表达式中,必须放在圆括号里面;

   function* demo() {
        console.log('hello' + yield 123);
       console.log('hello' + (yield 123));
   }

在这里插入图片描述
如果表达式用作函数参数或者放在赋值表达式的右边,可以不加括号。

   function* demo2() {
       let input = yield 123;

   }
for…of循环

for…of可以自动遍历Generator函数生成的遍历器对象,且此时不再需要调用next()方法

    function* foo() {
        yield 1;
        yield 2;
        yield 3;
        yield 4;
        return 5;
    }   
    for(let v of foo()){
        console.log(v);
    }

在这里插入图片描述
上面的例子使用for…of循环显示4条yield语句后面的值。一旦next()方法的返回对象的done属性为true,for…of循环就会终止,且不包含该返回对象。所以上述的return语句返回值不包括在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 > 10 ) break;
        console.log(n)
    }

在这里插入图片描述

[遍历对象]

利用for…of循环可以遍历任意对象的方法。原生的js对象没有遍历接口,无法直接使用for…of循环。但可以通过Generator函数为它加上这个接口。

    function* objectEntries1(obj) {
        let propKeys = Reflect.ownKeys(obj);
        for(let propKey of propKeys) {
            yield [propKey, obj[propKey]];
        }
    }
    let jane1 = {first: 'Jane', last:"Doe"};
    for(let [key, value] of objectEntries1(jane1)){
        console.log(`${key} : ${value}`);
    }

在这里插入图片描述
上面的原生jane对象不具备Iterator接口,无法使用for…of遍历。通过Generator函数objectEntries为它加上遍历器接口,这样就可以使用for…of遍历了。

还有一种写法是将Generator函数加到对象的Symbol.iterator属性上

    function* objectEntries2() {
        let propKeys =  Object.keys(this);
        for(let propKey of propKeys) {
            yield [propKey, this[propKey]];
        }
    }
    let jane2 = {first: 'Jane', last:"Doe"};
    jane2[Symbol.iterator] = objectEntries2;
    for(let [key, value] of jane2){
        console.log(`${key} : ${value}`);
    }

}

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

    function* numbers() {
        yield 1;
        yield 2;
        yield 3;
        return 4
    }
    // 扩展运算符
   console.log( [...numbers()]) 
    // 解构赋值
    let [x, y, z] = numbers();
   console.log([x,y,z]);
   
    // Array.from
   console.log( Array.from(numbers())) ;

在这里插入图片描述

yield*表达式

在Generator函数内部调用另一个Generator函数,默认情况下是没有效果的:

    function* foo () {
        yield 'a';
        yield 'b';
    }
    function* bar() {
        yield 'x';
        foo();
        yield 'y';
    }
    for( let v of bar()) {
        console.log(v);
    }

在这里插入图片描述

在zoo和bar都是Generator函数,在bar里面调用foo是不会有效果的。

使用yield*语句,可以实现在一个Generator函数里面执行另一个Generator函数

    function* foo () {
        yield 'a';
        yield 'b';
    }
    function* bar() {
        yield 'x';
        yield* foo();
        yield 'y'
    }
    for( let v of bar()) {
        console.log(v);
    }

在这里插入图片描述
等同于:

    function* bar () {
        yield 'x';
        for(let v of foo()){
            console.log(v);
        }
        yield 'y'
    }
    for( let v of bar()) {
        console.log(v);
    }

可以看到,yield*后面的Generator函数(没有return语句时)可以看做是for…of的一种简写形式。

当有return函数时,需要用var value = yield* generator()的形式获取return语句的值

    function* foo() {
        yield 2 ;
        yield 3 ;
        return 'foo';
    }
    
    function* bar () {
        yield 1;
        var v = yield* foo ();
        console.log('v:' + v);
        yield 4;
    }

    var a = bar();
    console.log(a.next().value); // 1
    console.log(a.next().value);// 2 
    console.log(a.next().value);// 3
    console.log(a.next().value);// v: foo 4
    console.log(a.next())// {value:undefined, done: true}

使用return语句的值需要使用新的变量来接受

    function* gen() {
        yield 'a';
        yield 'b';
        return 'the result';
    }

    function* log(generator) {
        let result = yield* generator;
        console.log(result)
    }

    // for(let value of log()){
    //     console.log(value);
    // }
    console.log([...log(gen())]);

在这里插入图片描述
上例存在两次遍历:第一次是扩展运算符遍历函数log返回的遍历器对象,第二次时yield*语句遍历函数gen返回的遍历器对象。

[yield*遍历数据]

任何数据只要有Iterator接口,就可以被yield*遍历

  function* gen() {
        yield* ['a', 'b', 'c'];
    }
    var k = gen();
    console.log(k.next().value);// a 
    console.log(k.next().value);// b
    console.log(k.next().value);// c

在这里插入图片描述
不加星号返回整个数组

    function* gen1() {
        yield ['a', 'b', 'c'];
    }
    console.log(gen1().next().value);// ['a', 'b', 'c']

字符串也可以被遍历,因为字符串也具有Iterator

    function* gen2() {
        yield* 'hello';
    }
    var y = gen2();
    console.log(y.next().value) // h
    console.log(y.next().value) // e

yield*命令可以很方便的取出嵌套数组的所有成员

    const tree = ['a', ['b', 'c'], ['d', 'e']];
    function* Tree(tree) {
        if(Array.isArray(tree)) {
            for(let i = 0; i < tree.length;i++) {
                yield* Tree(tree[i])
            }
        }else {
            yield tree;
        }
    }
    for(let v of Tree(tree)) {
        console.log(v);
    }

在这里插入图片描述
使用yield*语句遍历完全二叉树

     function eTree(left, label, right) {
         this.left = left;
         this.label = label;
         this.right = right;
    }
    // 中序遍历

    function *  order(t) {
        if(t) {
            yield* order(t.left);
            yield t.label;
            yield* order(t.right);
        }
    }
    
    // 生成二叉树
    function make(array) {
        // 判断是否为叶子结点
        if(array.length == 1) {
            return new eTree(null, array[0], null);
        }
        return new eTree(make(array[0]),array[1], make(array[2]));
    }

    let etree = make([[['a'], 'b',['c']], 'd', [['e'], 'f', ['g']]]);

    // 遍历二叉树
    var result = [];
    for(let node of order(etree)){
        result.push(node);
    }
    console.log(result);
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值