Generator 函数的this

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

  1. function* g() {}
  2. g.prototype.hello = function () {
  3. return 'hi!';
  4. };
  5. let obj = g();
  6. obj instanceof g // true
  7. obj.hello() // 'hi!'

上面代码表明,Generator 函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。

  1. function* g() {
  2. this.a = 11;
  3. }
  4. let obj = g();
  5. obj.next();
  6. obj.a // undefined

上面代码中,Generator 函数gthis对象上面添加了一个属性a,但是obj对象拿不到这个属性。

Generator 函数也不能跟new命令一起用,会报错。

  1. function* F() {
  2. yield this.x = 2;
  3. yield this.y = 3;
  4. }
  5. new F()
  6. // TypeError: F is not a constructor

上面代码中,new命令跟构造函数F一起使用,结果报错,因为F不是构造函数。

那么,有没有办法让 Generator 函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this

下面是一个变通方法。首先,生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。

  1. function* F() {
  2. this.a = 1;
  3. yield this.b = 2;
  4. yield this.c = 3;
  5. }
  6. var obj = {};
  7. var f = F.call(obj);
  8. f.next(); // Object {value: 2, done: false}
  9. f.next(); // Object {value: 3, done: false}
  10. f.next(); // Object {value: undefined, done: true}
  11. obj.a // 1
  12. obj.b // 2
  13. obj.c // 3

上面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。

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

一个办法就是将obj换成F.prototype

  1. function* F() {
  2. this.a = 1;
  3. yield this.b = 2;
  4. yield this.c = 3;
  5. }
  6. var f = F.call(F.prototype);
  7. f.next(); // Object {value: 2, done: false}
  8. f.next(); // Object {value: 3, done: false}
  9. f.next(); // Object {value: undefined, done: true}
  10. f.a // 1
  11. f.b // 2
  12. f.c // 3

再将F改成构造函数,就可以对它执行new命令了。

  1. function* gen() {
  2. this.a = 1;
  3. yield this.b = 2;
  4. yield this.c = 3;
  5. }
  6. function F() {
  7. return gen.call(gen.prototype);
  8. }
  9. var f = new F();
  10. f.next(); // Object {value: 2, done: false}
  11. f.next(); // Object {value: 3, done: false}
  12. f.next(); // Object {value: undefined, done: true}
  13. f.a // 1
  14. f.b // 2
  15. f.c // 3