Generator 函数的简单学习

转载及引用
ECMAScript 6 入门
Generator函数语法解析

一,Generator(生成器) 函数的理解

  • Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

  • 语法上,可以把理解成,Generator 函数是一个状态机,封装了多个内部状态。形式上,Generator 函数是一个普通函数。整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器,异步操作需要暂停的地方,都用yield语句。

  • 写法上: *号放在哪里好像都可以也。

function *gen () {}   
function* gen () {}
function * gen () {}
function*gen () {}
  • Generator函数特征:

    • (1) function 关键字和函数之间有一个星号(*),且内部使用yield表达式,定义不同的内部状态(可以有多个yield)。

    • (2) 调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象(也就是一个遍历器对象(Iterator Object))。

    • (3) 依次调用遍历器对象的next方法,遍历 Generator函数内部的每一个状态。

    • (4) 每调用一次next方法都会返回一个包含value和done属性的对象,此时会停留在某个yield表达式结尾处。value属性值即是yield表达式的值;done属性是布尔值,表示是否遍历完毕。

    function* fn(){   // 定义一个Generator函数
      yield 'hello';
      yield 'world';
      return 'end';
      }
      var f1 =fn();           // 调用Generator函数,函数并没有执行,返回的是一个Iterator对象
      console.log(f1);        // fn {[[GeneratorStatus]]: "suspended"}
      console.log(f1.next()); // {value: "hello", done: false}
      console.log(f1.next()); // {value: "world", done: false}
      console.log(f1.next()); // {value: "end", done: true}
      console.log(f1.next()); // {value: undefined, done: true} 
      console.log(f1.next()); // {value: undefined, done: true} 
    

    上面的代码中可以看到传统函数和Generator函数的运行是完全不同的,传统函数调用后立即执行并输出了返回值;Generator函数则没有执行而是返回一个Iterator对象,并通过调用Iterator对象的next方法来遍历,使得指针移向下一个状态。即:每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。函数体内的执行看起来更像是“被人踢一脚才动一下”的感觉.

  • Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

  • Generator 函数 返回的 Iterator对象 可以 一直向下遍历,通过判断 done 的值来确定是否 遍历结束。

二, yield 表达式

  1. 遍历器对象的next方法的运行逻辑如下。

    (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。

    (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。

    (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。

    (4)如果该函数没有return语句,则返回的对象的value属性值为undefined。

    需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针向下移动指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

    从上面的运行逻辑可以看出,返回的对象的value属性值有三种结果:

    (1)yield表达式后面的值
    (2)return语句后面的值
    (3)undefined
    

        yiled 和 return 的区别和联系: (1) return 只能有一个或者没有, yiled 可以有多个;   (2) 每次遇到 yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。  (3) 两个都可以返回紧跟在后面的表达式值。

  1. yeild 表达式只能用在 Generator函数 函数中,用在其它地方都会报错
    (function (){
        yeild 1;
    })()
    // 在一个普通函数中使用yield表达式,结果产生一个句法错误
    // Uncaught SyntaxError: Unexpected number

    var arr = [1, [[2, 3], 4], [5, 6]];

    var flat = function* (a) {
    a.forEach(function (item) {
        if (typeof item !== 'number') {
        yield* flat(item);
        } else {
        yield item;
        }
    });
    };
    //Uncaught SyntaxError: Unexpected identifier

    //上面代码也会产生句法错误,因为forEach方法的参数是一个普通函数,但是在里面使用了yield表达式(这个函数里面还使用了yield*表达式。一种修改方法是改用for循环。
  1. yield表达式如果用在另一个表达式之中,必须放在圆括号里面。作为函数参数和语句是可以不使用圆括号。
    function *gen () {
        console.log('hello' + yield) ×
        console.log('hello' + (yield)) √
        console.log('hello' + yield '凯斯') ×
        console.log('hello' + (yield '凯斯')) √
        foo(yield 1)  √
        const param = yield 2  √
    }
    function *gen () {
        console.log('hello' + (yield)) 
        console.log('hello' + (yield '凯斯')) 
        foo(yield 1)  
        const param = yield 2  
    }
    function foo(x) {
        console.log("value:"+x)
    }
    let ms = gen();
    console.log(ms.next())  //{ value: undefined, done: false }
    console.log(ms.next())  // helloundefined        { value: '凯斯', done: false }
    console.log(ms.next())   //helloundefined        { value: 1, done: false }
    console.log(ms.next())  //value:undefined        { value: 2, done: false }

三,next方法

  1. 每一次调用next方法,就会从函数头部或者上一次停下来的地方开始执行,直到遇到下一个yield表达式(return 语句)为止。同时,调用next方法时,会返回包含value和done属性的对象,value属性值可以为yield表达式、return语句后面的值或者undefined值,done属性表示遍历是否结束。

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

  3. yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

  4. next方法的参数

    (1) next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。相当于把 yield 表达式替换为参数值,

    (2) Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。也就是说,可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为。

    function *gen(x){
        let y = 2 + (yield (x*2));
        let m = yield (y + 1);
        return y + m + x;
    }
    
    let a = gen(1);
    console.log(a.next())   // { value: 2, done: false }
    console.log(a.next())   // { value: NaN, done: false }
    console.log(a.next())   // { value: NaN, done: true }
    /*
    上面代码中,第一次运行next方法的时候不带参数,执行yield (x*2),value:2;第二次运行next方法的时候不带参数,导致 y 的值等于2 + undefined(即NaN),y+1 后还是NaN,因此返回对象的value属性也等于NaN。第三次运行Next方法的时候不带参数,所以 m 等于undefined,返回对象的value属性等于 1 + NaN + undefined,即NaN。
    */
    
    let g = gen(1);
    console.log(g.next())   // { value: 2, done: false }
    console.log(g.next(2))  // { value: 5, done: false }
    console.log(g.next(5))   // { value:10 , done: true }
    
    /*
    上面代码第一次调用g的next方法时,返回x*2的值2; 第二次调用next方法,将上一次yield表达式的值设为2,因此y等于4,返回y+1的值5;第三次调用next方法,将上一次yield表达式的值设为5,因此m等于5,这时x等于1,y等于4,所以return语句的值等于10。
    */
    

    注意,由于next方法的参数表示上一个yield表达式的返回值,所以在第一次使用next方法时,传递参数是无效的。V8 引擎直接忽略第一次使用next方法时的参数,只有从第二次使用next方法开始,参数才是有效的。从语义上讲,第一个next方法用来启动遍历器对象,所以不用带有参数。

四,for…of循环

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

上面代码使用for…of循环,依次显示 3 个yield表达式的值。这里需要注意,一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的4,不包括在for…of循环之中。

  1. 除了for…of循环以外,扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。
   function *gen () {
       yield 1
       yield 2
       yield 3
       return 4
   }
   console.log([...gen()]);  // 1 2 3
   console.log(Array.from(gen()));  // 1 2 3

   let [x,y,z] = gen();
   console.log([x,y,z]);   // 1 2 3

五,yiled* 表达式

        如果在 Generator 函数内部,调用另一个 Generator 函数。需要在前者的函数体内部,手动完成遍历。

   function *gen() {
       yield 1
       yield 2
       yield 3
       return 4
   }
   function *foo(){
       yield "a";
       for(let i of gen()){
           console.log(i);
       }
       return 0;
   }
   let m = foo();
   console.log(m.next()) // { value: 'a', done: false }
   console.log(m.next())  //1   2   3    { value: 0, done: true }

   // 以上代码中可以看出来,如果有多个 Generator 函数嵌套,写起来就非常麻烦。

        yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。

        从语法角度看,如果yield表达式后面跟的是一个遍历器对象,需要在yield表达式后面加上星号,表明它返回的是一个遍历器对象。这被称为  yield*  表达式。

 function *gen() {
        yield 1
        yield 2
        yield 3
        return 4
    }
    function *foo(){
        yield "a";
        yield* gen();
        return 0;
    }
    let m = foo();
    console.log(m.next())  //{ value: 'a', done: false }
    console.log(m.next())  //{ value: 1, done: false }
    console.log(m.next())  //{ value: 2, done: false }
    console.log(m.next())  //{ value: 3, done: false }
    console.log(m.next())  //{ value: 0, done: true }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值