21.7 基于迭代 API 的继承关系(包含生成器)
下面的图展示了在 ECMAScript 6 中各种对象之间的关系(此图基于 ECMAScript 规范中 Allen Wirf-Brock 的图):
说明:
- 空心箭头表示两个对象的继承关系。换句话说,从 x 指向 y 的箭头意味着
Object.getPrototypeOf(x) === y
。 - 圆括号表示当前被包起来的对象是存在的,但是不能通过全局变量来访问。
- 带有
instanceof
字眼的箭头如果从 x 指向 y ,就表明x instanceof y
。o instanceof C
实际上就相当于C.prototype.isPrototypeOf(o)
- 带有
prototype
字眼的箭头如果从 x 指向 y ,就表明x.prototype === y
。
此图揭示了两个有趣的事实:
第一个,生成器函数 g
很像构造函数(甚至可以通过 new 来调用它,这和直接调用的效果是一样的):它创建的生成器对象是它的实例,添加到 g.prototype
上的方法成为原型方法,等等:
> function* g() {}
> g.prototype.hello = function () { return 'hi!'};
> let obj = g();
> obj instanceof g
true
> obj.hello()
'hi!'
第二个,如果你想给所有的生成器对象添加方法,最好将这些方法添加到 (Generator.object)
上。访问这个对象的一种方式如下:
> let Generator_prototype = Object.getPrototypeOf(function* () {}).prototype;
> Generator_prototype.hello = function () { return 'hi!'};
> let generatorObject = (function* () {})();
> generatorObject.hello()
'hi!'
21.7.1 IteratorPrototype
在图中没有 (Iterator)
,因为不存在这样的对象。但是,考虑到 instanceof
的工作原理,同时也因为 (IteratorPrototype)
是 g1()
的原型,你仍然可以说 g1()
是 Iterator
的实例。
ES6 中所有的迭代器在原型链中都有 (IteratorPrototype)
。这个对象是可迭代的,因为它有下面的方法。因此,所有的 ES6 迭代器都是可迭代的(因此,你可以在这些对象上应用 for-of
)。
[Symbol.iterator]() {
return this;
}
规范推荐使用如下代码访问 (IteratorPrototype)
:
const proto = Object.getPrototypeOf.bind(Object);
let IteratorPrototype = proto(proto([][Symbol.iterator]()));
也可以使用:
let IteratorPrototype = proto(proto(function* () {}.prototype));
引用 ECMAScript 6 规范中的话:
ECMAScript 代码可能定义继承自 IteratorPrototype 的对象。IteratorPrototype 对象提供了一个给所有可迭代对象添加 额外方法的地方。
在后续的 ECMAScript 版本中,也许可以直接获取到 IteratorPrototype ,并且包含一些工具方法,比如 map()
和 filter()
(来源) 。
21.7.2 生成器中的 this
值
一个生成器函数混合了两方面的内容:
- 1、它是一个设置和返回生成器对象的函数。
- 2、它包含了生成器对象执行过程的代码。
这就是为什么生成器函数中的 this
值不太好猜测的原因。
在函数调用和方法调用中, this
值和其作为普通函数(非生成器函数)的时候一致:
function* gen() {
'use strict'; // just in case
yield this;
}
// Retrieve the yielded value via destructuring
let [functionThis] = gen();
console.log(functionThis); // undefined
let obj = { method: gen };
let [methodThis] = obj.method();
console.log(methodThis === obj); // true
如果通过 new
调用一个生成器函数,那么在函数中访问 this 将会得到一个 ReferenceError
错误(源自 ES6 规范):
function* gen() {
console.log(this); // ReferenceError
}
new gen();
有一个变通的方法,就是把生成器函数包裹在一个普通函数里面,通过 next()
传入生成器自身的生成器对象。这意味着生成器必须使用第一个 yield
拿到自身的生成器对象:
let generatorObject = yield;