generator概念的理解

generator它的出现其实就是为了解决异步方案、可以将generator理解成是一个状态机、
同时他也是迭代器iterator的实现的语法糖。

generator的定义和使用

generator其实就是在函数前面加个* 然后函数里面使用了yield字段。
举一个简单例子

定义

  1. function* test(){
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. }

使用

  1. let t = test();
  2. t.next();//{value:1,done:false}
  3. t.next();//{value:2,done:false}
  4. t.next();//{value:3,done:false}
  5. t.next();//{value:undefined,done:true}

总结一下generator 包含两个部分一个是在函数外面使用了* 然后函数里面使用了yield字段 这样就构成了一个简单generator

generator中的* 星号

上面的例子 如何generator函数中也可以不直接使用yield字段、也是可以的。
也是正常调用的、和普通函数一样。

举个例子

  1. function * test(){
  2. console.log('aa');
  3. console.log('bb');
  4. }
  5. var p = test();//这里里面结果并不会打印
  6. //需要调用
  7. p.next()
  8. //打印 aa bb

generator中yield

generator是由*(星)和yield两个部分包含的。
那么yield字段不可以单独在函数内部直接使用。

yield字段不可以单独在函数内部直接使用

举个例子

  1. function test(){
  2. yield 1;
  3. }

这里会报错 VM501:2 Uncaught SyntaxError: Unexpected number

yield字段作为表达式必须放在圆括号里

  1. function* demo() {
  2. console.log('Hello' + yield); // SyntaxError
  3. console.log('Hello' + yield 123); // SyntaxError
  4. console.log('Hello' + (yield)); // OK
  5. console.log('Hello' + (yield 123)); // OK
  6. }

yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。

  1. function* demo() {
  2. foo(yield 'a', yield 'b'); // OK
  3. let input = yield; // OK
  4. }

Generator.prototype.next

generator中的next是用于调用yield返回的值。而yield表达式本身没有返回值,或者说总是返回undefinednext方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

next可以传参且该参数会作为上一次yield表达式的返回值

举个例子

  1. function *test(a){
  2. var b = yield 1 + a;
  3. var c = yield 2 + b;
  4. return a + b + c
  5. }
  6. //调用test
  7. var tt = test(10);
  8. //第一次调用 //yield 1+10 => 11 (a = 10)
  9. tt.next() //11
  10. //第二次调用 //这里首先的执行的是 b = yiled 20;(b = 20); 然后是yield = 2 + 20 => 22
  11. tt.next(20) //22
  12. //第三次调用 //这里会执行上一次的 c = yield 30; a+b+c = 10 + 20 + 30 = 60
  13. tt.next(30) //60

Generator.prototype.return

这里的return可以等价于在内部定义的return 返回是一样的。

在内部使用return

  1. //函数内部使用return
  2. function* test(){
  3. yield 1;
  4. yield 2;
  5. return 3;
  6. yield 4;
  7. }
  8. //调用
  9. for(let i of test()){
  10. console.log(i)
  11. }
  12. //打印结果是1 2 后面就会打印

在外部使用return

  1. function* test(){
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. yield 4;
  6. }
  7. //调用
  8. var tt = test();
  9. for(let i of tt){
  10. if(i == 2) {
  11. tt.return('stop')
  12. }
  13. console.log(i);
  14. }

Generator.prototype.throw

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

  1. var g = function* () {
  2. try {
  3. yield;
  4. } catch (e) {
  5. console.log('内部捕获', e);
  6. }
  7. };
  8. var i = g();
  9. i.next();
  10. try {
  11. i.throw('a');
  12. i.throw('b');
  13. } catch (e) {
  14. console.log('外部捕获', e);
  15. }
  16. // 内部捕获 a
  17. // 外部捕获 b

上面代码中,遍历器对象i连续抛出两个错误。第一个错误被 Generator 函数体内的catch语句捕获。i第二次抛出错误,由于 Generator 函数内部的catch语句已经执行过了,不会再捕捉到这个错误了,所以这个错误就被抛出了 Generator 函数体,被函数体外的catch语句捕获。

next()&throw()&return()区别

generator.prototype.next、generator.prototype.throw、generator.prototype.return都是Generator原型提供全局方法。他们三者在理解上可以是一个替换作用。next()throw()return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。

next()是将yield表达式替换成一个值。

  1. const g = function* (x, y) {
  2. let result = yield x + y;
  3. return result;
  4. };
  5. const gen = g(1, 2);
  6. gen.next(); // Object {value: 3, done: false}
  7. gen.next(1); // Object {value: 1, done: true}
  8. // 相当于将 let result = yield x + y
  9. // 替换成 let result = 1;

上面代码中,第二个next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined

throw()是将yield表达式替换成一个throw语句。

  1. gen.throw(new Error('出错了')); // Uncaught Error: 出错了
  2. // 相当于将 let result = yield x + y
  3. // 替换成 let result = throw(new Error('出错了'));

return()是将yield表达式替换成一个return语句。

  1. gen.return(2); // Object {value: 2, done: true}
  2. // 相当于将 let result = yield x + y
  3. // 替换成 let result = return 2;

yield* 表达式

yield可以理解成是for…of的语法糖。

举个例子

function *test(){
  yield 1;
  yield 2;
}

function *test2(){
  yield 3;
  yield 4;
}

function *test3(){
  yield* test();
  yield* test2();
}

//等上面的方法等价于
function *test3(){
 for(let i of test()){yield(i)}
 for(let i of test2()){yield(i)}
}

//也等价于
function *test3(){
  yield 1;
  yield 2;
  yield 3;
  yield 4;
}

作为对象属性的Generator的函数

举个例子

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

//或者
var obj = {
  test:function *() {
   yield 1;
   yield 2;
   return 3;
  }
}

generator中的this

this默认下只能被当做函数来使用

generator之前在使用时 一直都是把它作为函数来使用。
如果是new方式来调用呢?
举个例子

function *hh (){
  yield this.a = 1;
  yield this.b = 2;
}

//new 这个hh
var obj = new hh();
//这里会报错  VM2171:1 Uncaught TypeError: hh is not a constructor
//at <anonymous>:1:11

this可以改变指向作为对象使用

那么,有没有办法让 Generator 函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this
下面是一个变通方法。首先,生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var obj = {};
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内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。

上面代码中,执行的是遍历器对象f,但是生成的对象实例是obj,有没有办法将这两个对象统一呢?
一个办法就是将obj换成F.prototype

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
var f = F.call(F.prototype);
f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3

再将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();
f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}
f.a // 1
f.b // 2
f.c // 3

参考es6文档 https://es6.ruanyifeng.com/#docs/generator