ES6 Generator函数 基本用法详解

简介

Generator是ES6提供的一种异步编程的解决方案,语法行为和传统函数完全不一样,Generator函数有多种理解角度,从语法上来讲,它是一个状态机,内部封存着多个状态,那么执行Generator函数会返回一个遍历器(Iterator)对象。
从形式上来讲,Generator是一个普通函数,但是它有两个特征,一是funciton函数名之间有个星号(*),而是函数内部使用yield表达式,定义不同的内部状态。

基本用法

        function* test(){
            yield 1;
            yield 2;
            return 3;
        }
        var start=test();

如上代码所示,Generator函数test内部有两个yield表达式,即该函数有三个状态,1,2,return语句(结束执行)。
Generator函数的调用和普通函数调用一样,不同的时候,调用Generator函数的时候,并没有执行,也没有返回执行结果,而是返回一个指向内部状态的指针对象,也就是遍历器对象
那么,怎么执行它呢?这时候必须调用遍历器对象的next方法。

        console.log(start.next())//{value: 1, done: false}
        console.log(start.next())//{value: 2, done: false}
        console.log(start.next())//{value: 3, done: true}

可以看出每次调用遍历器对象的next方法,就会返回一个有着valuedone两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。
简单的说,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

yield表达式

上面已经提过,yield表达式是一个暂停标记,每一个yield表达式代表着就是一种状态,而执行这种状态,需要调用遍历器的next方法,下面咱们就通过一个例子讲下next的运行逻辑。

        function* test(){
            console.log(1)
            yield 1;
            console.log(2)
            yield 2;
            console.log(3)
            return 3;
        }
        var start=test();
        console.log(start.next())
        console.log(start.next())
        console.log(start.next())
  • 第一次执行next方法,代表Generator函数开始执行,那么首先打印出1,这时候遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值,那么第一个调用next函数的结果就是{value: 1, done: false}
  • 第二次调用next方法,再继续往下执行,首先打印出2,遇到yield表达式时暂停,那么第二次结果{value: 2, done: false}
  • 如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值,然后把返回对象的done属性值置为true
  • 如果该函数没有return语句,则返回的对象的value属性值为undefined,done属性值依然为true

上面例子,在控制台结果如下所示:
在这里插入图片描述
需要特别注意的是:
yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

(function (){
  yield 1;
})()
// SyntaxError: Unexpected number

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

function* demo() {
  console.log('Hello' + yield); // SyntaxError
  console.log('Hello' + yield 123); // SyntaxError

  console.log('Hello' + (yield)); // OK
  console.log('Hello' + (yield 123)); // OK
}

next方法的参数

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

        function* test(x){
            var y=2*(yield x+1);
            var z=yield y+2;
            return x+y+z;
        }
        var start=test(1);
        console.log(start.next())
        console.log(start.next())
        console.log(start.next())

以上这个例子,next没有带参数,那么结果就是
在这里插入图片描述
可以看到,第二,三次next的返回值为NaN,是因为yield的返回值为undefined,那么undefined加数组就等于NaN,接下来,咱们再next方式传入参数,

        function* test(x){
            var y=2*(yield x+1);
            var z=yield y+2;
            return x+y+z;
        }
        var start=test(1);
        console.log(start.next())
        console.log(start.next(2))
        console.log(start.next(3))

在这里插入图片描述
分析下:

  • 调用Generator函数test,传入一个参数1,接下来执行遍历器的next方法,第一次执行,遇到yield x+1,返回结果{value:2,done:false}
  • 第二次执行next方法,并传入参数2,这时候,2就是上一次yield表达式的值,也就是yield x+1的值,那么y就等于4,这时候遇到第二个yield表达式,暂停,返回结果{value:6,done:false}
  • 第三次执行next方法,并传入参数3,这时候,3就是上一次yield表达式的值,也就是yield y+2的值,那么z就等于3,这时候遇到return语句,结束执行,返回结果{value:8,done:false}

需要注意的是:
由于next参数代表的是上一次yield表达式的值,所以第一次执行next方法,传递参数是无效的,从语义上来讲,第一次执行next方法是用来启动遍历器对象的,所以不用带参数。

for…of循环

for...of循环可以自动遍历 Generator 函数运行时生成的Iterator对象,且此时不再需要调用next方法

     function* test(x){
           yield 1;
           yield 2;
           yield 3;
           yield 4;
           yield 5;
           yield 6;
           return 7
        }
        for(let i of test()){
            console.log(i)//1 2 3 4 5 6 
        }

上面代码细心的朋友会发现少打印了一个7,这是因为一旦next方法的返回对象的done属性为true,for…of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的7,不包括在for…of循环之中。
除了for...of循环以外,扩展运算符(…)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数

function* numbers () {
  yield 1
  yield 2
  return 3
  yield 4
}

// 扩展运算符
[...numbers()] // [1, 2]

// Array.from 方法
Array.from(numbers()) // [1, 2]

// 解构赋值
let [x, y] = numbers();
x // 1
y // 2

// for...of 循环
for (let n of numbers()) {
  console.log(n)
}
// 1
// 2

Generator.prototype.throw()

Generator 函数返回的遍历器对象,都有一个throw方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

        var throwOperator = function* () {
            try {
                yield;
            } catch (e) {
                console.log('内部捕获', e);
            }
        };

        var i = throwOperator();
        i.next();

        try {
            i.throw('a');
            i.throw('b');
        } catch (e) {
            console.log('外部捕获', e);
        }
        //内部捕获
        //外部捕获

上面例子,遍历器对象i连续抛出两个错误,第一个由内部捕获,第二个由外部捕获,这是因为,第一次catch执行过,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。
需要注意几点:

  • 遍历器对象的throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法。
  • 如果调用了遍历器对象的throw方法,如果Generator函数内部没有try..catch,那么将会被外部的try..catch捕获,如果都没有将报错,另外需要注意的是,不要混淆throw方法和遍历器对象的throw方法,前者是只能被外部的try..catch捕获。
  • 遍历器对象的throw方法被函数内部捕获以后,会附带执行下一条yield表达式。也就是说,会附带执行一次next方法,反而如果没有捕获
    var gen = function* gen(){
      try {
        yield console.log('a');
      } catch (e) {
       
      }
      yield console.log('b');
      yield console.log('c');
    }
    
    var g = gen();
    g.next() // a
    g.throw() // b
    g.next() // c
    
    反而如果没有被内部捕获,那么就不会再执行下去了。
        var gen = function* gen() {
            yield console.log('a');
            yield console.log('b');
            yield console.log('c');
        }
        try {
            var g = gen();
            g.next() // a
            g.throw() // b
            g.next() // c
        } catch (e) {
            console.log(e)
        }
        // a
        //"报错"
    
  • throw命令与遍历器对象的throw方法是无关的,两者互不影响。
    	var gen = function* gen(){
    	  yield console.log('hello');
    	  yield console.log('world');
    	}
    	
    	var g = gen();
    	g.next();
    	
    	try {
    	  throw new Error();
    	} catch (e) {
    	  g.next();
    	}
    

Generator.prototype.return()

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: true }
g.next()        // { value: undefined, done: true }

需要注意的是:
如果 Generator 函数内部有try…finally代码块,且正在执行try代码块,那么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 }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

yield* 表达式

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

		function* foo() {
		  yield 'a';
		  yield 'b';
		}
		
		function* bar() {
		  yield 'x';
		  // 手动遍历 foo()
		  for (let i of foo()) {
		    yield i
		  }
		  yield 'y';
		}
		
		for(let i of bar()){
			console.log(i)//x a b y
		}

有没有发现很麻烦,所以ES6 提供了yield*表达式,作为解决办法,用来在一个 Generator 函数里面执行另一个 Generator 函数。

        function* foo() {
            yield 'a';
            yield 'b';
        }

        function* bar() {
            yield 'x';
            yield* foo()
            yield 'y';
        }

        for (let i of bar()) {
            console.log(i)//x a b y
        }

需要注意的是:
如果被代理的 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, done: false}
it.next()
// {value: 2, done: false}
it.next()
// {value: 3, done: false}
it.next();
// "v: foo"
// {value: 4, done: false}
it.next()
// {value: undefined, done: true}

上面代码可以看出第四次调用next方法时,遇到return,会提供一个返回值。

Generator函数的this

Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

        function* g() { }

        g.prototype.hello = function () {
            console.log("hello")
        };

        let obj = g();

        obj instanceof g // true
        obj.hello() // 'hi!'

但是,需要注意的是:

  • 如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。
            function* g() {
                this.name="hty"
             }
            let obj = g();
            console.log(obj.name)// undefined
    
  • Generator函数不能和new一起使用,因为它并不是构造函数。

那么,有个问题,怎么既可以用next方法,又可以获得正常的this?最好的解决方案就是,把this绑到Generator函数的prototype上。

        function* g() {
            this.name="hty"
            yield this.sex="男"
            yield this.age="25"
            return this.lover="xxx"
         }
        let obj = g.call(g.prototype);
        console.log(obj.next())//{value: "男", done: false}
        console.log(obj.name)// "hty"
        console.log(obj.sex)// "男"
        console.log(obj.next())//{value: "25", done: false}
        console.log(obj.age)// "25"
        console.log(obj.next())//{value: "xxx", done: true}
        console.log(obj.lover)// "xxx"

这里需要注意,只有执行next方法,才能把内部属性绑定到obj上。

参考链接

阮博士的ES6入门

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值