15.5 种模式
在 ECMAScript 6 中,另一种使内置的构造器变得可扩展的机制:如果一个方法(比如 Array.prototype.map()
返回一个新实例),应该用什么构造器来创建这个实例呢?
在接下来的小节中会使用如下的辅助函数:
function isObject(value) {
return (value !== null
&& (typeof value === 'object'
|| typeof value === 'function'));
}
/**
* Spec-internal operation that determines whether `x` can be used as a constructor.
*/
function isConstructor(x) {
···
}
15.5.1 标准的种模式
定制使用方法(比如 Array.prototype.map()
)创建的实例的模式被称为种模式:
- 如果存在
this.constructor[Symbol.species]
,使用它作为新实例的构造器。 - 否则,使用默认的构造器(例如数组的
Array
)。
用 JavaScript 代码实现,这种模式看起来像这样:
function SpeciesConstructor(O, defaultConstructor) {
let C = O.constructor;
if (C === undefined) {
return defaultConstructor;
}
if (! isObject(C)) {
throw new TypeError();
}
let S = C[Symbol.species];
if (S === undefined || S === null) {
return defaultConstructor;
}
if (! isConstructor(S)) {
throw new TypeError();
}
return S;
}
数组的标准种模式在规范是通过 SpeciesConstructor() 实现的。
15.5.2 数组的种模式
下面代码大致描述了种模式是如何应用于数组的:
function ArraySpeciesCreate(originalArray, length) {
let C = undefined;
if (Array.isArray(originalArray)) {
C = originalArray.constructor;
if (isObject(C)) {
C = C[Symbol.species];
}
}
if (C === undefined || C === null) {
return new Array(length);
}
if (! IsConstructor(C)) {
throw new TypeError();
}
return new C(length);
}
Array.prototype.map()
返回的数组通过 ArraySpeciesCreate(this, this.length)
创建。
数组种模式在规范中是通过 ArraySpeciesCreate() 操作实现的。
15.5.3 静态方法中的种模式
Promise 中使用了大量的静态方法种模式,例如 Promise.all():
let C = this; // default
if (! isObject(C)) {
throw new TypeError();
}
// The default can be overridden via the property `C[Symbol.species]`
let S = C[Symbol.species];
if (S !== undefined && S !== null) {
C = S;
}
if (!IsConstructor(C)) {
throw new TypeError();
}
let instance = new C(···);
15.5.4 在子类中覆盖默认的 spieces
下面所示是 [Symbol.species]
的默认 getter :
get [Symbol.species]() {
return this;
}
默认的 getter
在内置的类 Array
, ArrayBuffer
, Map
, Promise
, RegExp
, Set
和 %TypedArray%
中都有实现,并且自动地被这些内置类的子类继承。
有两种方式可以覆盖默认的 species
:使用自定义的构造器或者使用 null
。
15.5.4.1 设置自定义构造器的 species
可以通过静态的 getter (行 A )覆盖默认的 species
:
class MyArray1 extends Array {
static get [Symbol.species]() { // (A)
return Array;
}
}
这样一来, map()
就返回 Array
的实例了:
let result1 = new MyArray1().map(x => x);
console.log(result1 instanceof Array); // true
如果不覆盖默认的 species
, map()
就会返回子类的实例:
class MyArray2 extends Array { }
let result2 = new MyArray2().map(x => x);
console.log(result2 instanceof MyArray2); // true
15.5.4.2 通过数据属性指定 species
如果不想使用静态的 getter ,那么就要使用 Object.defineProperty()
。你可以使用赋值,这样会触发 setter
,而这个 setter
并不存在(只有一个 getter )。
例如,这里我们设置 MyArray1
的 species
为 Array
:
Object.defineProperty(
MyArray1, Symbol.species, {
value: Array
});
15.5.4.3 将 species
设置为 null
如果将 species
设置为 null
,就会使用默认的构造器(选用哪个构造器决定于使用哪一个种模式变体,参考前面的小节获取更多相关信息)。
class MyArray3 extends Array {
static get [Symbol.species]() {
return null;
}
}
let result3 = new MyArray3().map(x => x);
console.log(result3 instanceof Array); // true