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