zoukankan      html  css  js  c++  java
  • ES6 |Generator函数语法

    在这里插入图片描述

    Generator函数

    Generator 函数是 ES6 提供的一种异步编程解决方案。Generator 函数在形式上具有两个特征:

    一、function关键字与函数名之间有一个星号;

    二、函数体内部使用yield语句,定义不同的内部状态

    function* helloWorldGenerator() {
      yield 'hello';	//hello状态
      yield 'world';	//world状态
      return 'ending';	//结束
    }
    

    调用Generator函数后返回指向内部状态的指针对象,也就是遍历器对象。必须调用遍历器对象的next方法,才能使得指针移向下一个状态

    var hw = helloWorldGenerator();
    hw.next()// { value: 'hello', done: false }
    hw.next()// { value: 'world', done: false }
    hw.next()// { value: 'ending', done: true }
    hw.next()// { value: undefined, done: true }
    

    yield语句

    yield语句就像暂停标志,每次next方法碰到yield就会暂停执行后面的操作,并将yield后面的表达式作为返回对象的value值;下一次调用next时再执行,直到遇到下一个yield,以此类推,碰到return时将后面的表达式作为返回对象的value值,如果return后面没有语句,那么返回对象的value就是undefined

    function* gen() {
      yield  123 + 456;
    }
    gen().next() //{value: 579, done: false}
    
    • yield语句如果用在一个表达式之中,必须放在圆括号里面

      function* demo() {
        console.log('Hello' + (yield)); // OK
        console.log('Hello' + (yield 123)); // OK
      }
      
    • yield语句用作函数参数或放在赋值表达式的右边,可以不加括号

      function* demo() {
        foo(yield 'a', yield 'b'); // OK
        let input = yield; // OK
      }
      

    与 Iterator 接口的关系

    Generator函数就是遍历器生成函数,因此可以把Generator赋值给对象的Symbol.iterator属性,从而使得该对象具有Iterator接口

    var myIterable = {};
    myIterable[Symbol.iterator] = function* () {
      yield 1;
      yield 2;
    };
    
    function* gen(){}
    var g = gen();	//函数返回的对象
    g[Symbol.iterator]() === g	//执行Symbol.iterator后指向自己
    

    状态可遍历

    function* numbers () {
      yield 1
      yield 2
      return 3
      yield 4
    }
    
    //1. 扩展运算符
    [...numbers()] // [1, 2]
    
    //2. Array.from 方法
    Array.from(numbers()) // [1, 2]
    
    //3. 解构赋值
    let [x, y] = numbers();
    x // 1
    y // 2
    
    //4. for...of 循环
    for (let n of numbers()) {
      console.log(n)
    }
    // 1
    // 2
    
    //for...of注意的点:
    function *foo() {
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      yield 5;
      return 6;
    }
    
    for (let v of foo()) {
      console.log(v);
    }
    // 1 2 3 4 5
    //解析:一旦next方法返回对象的done为true,循环就终止,切不包含该返回对象,所以最后结果没有6
    

    next方法的参数

    next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值

    方法

    • Generator.prototype.throw():可以在函数体外抛出错误,然后在Generator函数体内捕获

      var g = function* () {
        try {yield;
        } catch (e) {
          console.log('内部捕获', e);
        }
      };
      
      var i = g();
      i.next();	
      //返回对象{value:undefined,done:false},如果不返回内部无法捕获到错误,最后只打印出“外部捕获 a”
      
      try {
        i.throw('a'); //用遍历器对象的throw方法抛错
        i.throw('b');
      } catch (e) {
        console.log('外部捕获', e);
      }
      
      // 内部捕获 a
      // 外部捕获 b
      //解析:遍历器对象i连续抛出两个错误,第一个被Generator函数体内的catch语句捕获,由于第一个错误已经被捕获,所以在函数外只捕获第二个错误b
      
      //注意:如果是全局的throw命令抛出错误,则只能被函数体外的catch捕获
      //如果Generator函数内部没有部署try...catch代码块,那么throw方法抛出的错误,将被外部try...catch代码块捕获
      //如果Generator函数内部和外部,都没有部署try...catch代码块,那么程序将报错,直接中断执行
      

      throw方法可以接受一个参数,该参数会被catch语句接收

      var g = function* () {
        try {
          yield;
        } catch (e) {
          console.log(e);
        }
      };
      
      var i = g();
      i.next();
      i.throw(new Error('出错了!'));	
      // Error: 出错了!(…)
      

      throw方法被捕获以后,会附带执行下一条yield语句,即执行一次next方法。

      var gen = function* gen(){
        try {
          yield console.log('a');
        } catch (e) {
          // ...
        }
        yield console.log('b');
      }
      
      var g = gen();
      g.next() // a
      g.throw() // b
      

      Generator函数体内抛出的错误,也可以被函数体外的catch捕获

      function* foo() {
        var x = yield 3;
        var y = x.toUpperCase();
        yield y;
      }
      
      var it = foo();
      it.next(); // { value:3, done:false }
      
      try {
        it.next(42);	//传入一个数值42,但数值没有toUpperCase(),内部会抛错
      } catch (err) {
        console.log(err);	//这时被函数外的catch捕获
      }
      

      一旦Generator执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,将返回一个value属性等于undefineddone属性等于true的对象

    • Generator.prototype.return():返回给定的值,并且终结遍历Generator函数

      function* gen() {
        yield 1;
        yield 2;
        yield 3;
      }
      var g = gen();
      
      //情况1:return不带参数
      g.next()        // { value: 1, done: false }
      g.return() // { value: undefined, done: true } //终止了
      
      //情况2:return带参数
      g.next()        // { value: 1, done: false }
      g.return('foo') // { value: "foo", done: true }	//value为改参数,终止
      g.next()        // { value: undefined, done: true }
      

      如果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, done: false }
      g.next() // { value: 2, done: false }
      g.return(7) // { value: 4, done: false } //调用后开始执行finally代码块
      g.next() // { value: 5, done: false }
      g.next() // { value: 7, done: true }	//等finally代码块执行完再返回
      

    yield* 语句

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

    function* foo() {
      yield 'a';
      yield 'b';
    }
    
    function* bar() {
      yield 'x';
      foo();	//调用另一个Generator 函数
      yield 'y';
    }
    for (let v of bar()){
      console.log(v);	
    }
    // "x"
    // "y"
    

    yield*作用:实现在 Generator 函数里执行另一个 Generator 函数。

    function* bar() {
      yield 'x';
      yield* foo();	//在调用Generator 函数前加上yield*;
      yield 'y';
    }
    
    // 等同于
    function* bar() {
      yield 'x';
      yield 'a';
      yield 'b';
      yield 'y';
    }
    
    // 等同于
    function* bar() {
      yield 'x';
      for (let v of foo()) {
        yield v;
      }
      yield 'y';
    }
    
    for (let v of bar()){
      console.log(v);
    }
    // "x"
    // "a"
    // "b"
    // "y"
    

    对比

    function* bar() {
      yield 'x';
      yield foo();	//如果没有加*,将返回一个本身,在这里即遍历器对象
      yield 'y';
    }
    for (let v of bar()){
      console.log(v);
    }
    //x
    //foo {...}
    //y
    

    这表明yield*返回的是一个遍历器对象

    如果yield*后面跟着数据结构只要有Iterator接口,就会遍历

    function* gen(){
      yield* ["a", "b", "c"];
    }
    gen().next() // { value:"a", done:false }
    

    作为对象属性的Generator函数

    //写法1:
    let obj = {
      * myGeneratorMethod() {··· }
    };
    //写法2:
    let obj = {
      myGeneratorMethod: function* () {// ···}
    };
    

    Generator函数的this

    引入

    function* g() {}	//一个Generator函数
    g.prototype.hello = function () {	//添加原型
      return 'hi!';
    };
    
    let obj = g();
    obj instanceof g // true	//表明obj是g生成的实例
    obj.hello() // 'hi!'	//表明obj继承了g的原型
    

    对比:如果把g当作普通的构造函数,不会生效。这是因为g返回的总是遍历器对象,而不是this对象,所以也不能通过new的方式跟g构造函数一起使用

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

    如何使Generator函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this

    function* F() {
      this.a = 1;
      yield this.b = 2;
      yield this.c = 3;
    }
    
    //1. 生成一个空对象
    var obj = {};
    //2. 用call绑定Generator函数内部的this
    var f = F.call(obj);
    
    f.next();  // Object {value: 2, done: false}
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}
    obj.a // 1
    obj.b // 2
    obj.c // 3
    

    执行的是遍历器对象f,而生成的对象实例是obj,有没有办法将这两个对象统一呢?

    function* F() {
      this.a = 1;
      yield this.b = 2;
      yield this.c = 3;
    }
    //解决:将obj换成F.prototype,这样就可以统一用f既是遍历器对象,又是实例对象了
    var f = F.call(F.prototype);
    
    //再将F改成构造函数,就可以对它执行new命令了
    function* gen() {
      this.a = 1;
      yield this.b = 2;
      yield this.c = 3;
    }
    
    function F() {
      return gen.call(gen.prototype);
    }
    
    var f = new F();
    

    应用

    • 异步操作的同步化表达

      Generator函数的暂停执行的效果,意味着可以把异步操作写在yield语句里面,等到调用next方法时再往后执行,这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在yield语句下面,反正要等到调用next方法时再执行。所以,Generator函数的一个重要实际意义就是用来处理异步操作,改写回调函数。

      function* main() {
        var result = yield request("http://some.url");
        var resp = JSON.parse(result);
          console.log(resp.value);
      }
      
      function request(url) {
        makeAjaxCall(url, function(response){
          it.next(response);//next方法必须加上response参数,因为yield语句构成的表达式本身是没有值的
            //第二次next将response作为上一次yield的返回值,赋值给了resp
        });
      }
      
      var it = main();
      it.next();	//第一次next将执行语句准备好
      
    • 控制流管理

    • 部署Iterator接口

    • 作为数据结构

  • 相关阅读:
    自己写的DBHelper感慨颇深
    23种设计模式:观察者模式,第一次对观察者模式理解的这么通透。
    自己用心写的 存储过程分页 给自己的平台用
    开篇成长的开始[废话一把]
    C# 中i++在ref参数调用下的有趣现象
    点点滴滴的成长[2011111]:理解C#修饰符
    点点滴滴的成长[2011114]:自定义config文件
    扩展方法在Json字符串转化中的应用
    jquery学习二:jquery性能优化
    javascript系列1:函数
  • 原文地址:https://www.cnblogs.com/sanhuamao/p/13595776.html
Copyright © 2011-2022 走看看