在JavaScript中,当我们继承内置类型比如Array、Map或者Set时,经常会遇到一个奇怪的问题:子类实例调用父类的方法时,返回的新对象并不是子类类型,而是父类的原始类型。这时候就需要用到Symbol.species来解决这个问题。

什么是Symbol.species
Symbol.species是JavaScript中的一个知名符号,它被内置类型用来指定在返回派生对象时应该使用的构造函数。默认情况下,内置类型的方法在返回新对象时,会使用当前实例的constructor属性来创建新对象,但如果我们修改了这个属性,或者希望自定义返回的类型,就可以通过Symbol.species来指定。
它本质上是一个静态的getter,定义在类的内部,用来返回一个构造函数,这个构造函数会被内置类型的方法用来创建新的实例。
如何定义Symbol.species
定义Symbol.species很简单,只需要在子类中添加一个静态的getter,名为Symbol.species,返回你希望使用的构造函数即可。下面是一个基础的示例,展示如何为自定义的数组子类定义Symbol.species:
// 定义一个继承自Array的子类
class MyArray extends Array {
// 定义Symbol.species静态getter
static get [Symbol.species]() {
// 返回Array构造函数,这样调用数组方法时返回的是普通Array实例
return Array;
}
// 自定义子类的方法
sum() {
return this.reduce((acc, cur) => acc + cur, 0);
}
}
// 创建MyArray实例
const myArr = new MyArray(1, 2, 3);
console.log(myArr instanceof MyArray); // true
console.log(myArr instanceof Array); // true
// 调用slice方法,slice是Array的内置方法
const sliced = myArr.slice(0, 2);
console.log(sliced instanceof MyArray); // false,因为Symbol.species返回的是Array
console.log(sliced instanceof Array); // true
console.log(sliced.sum); // undefined,因为返回的是Array实例,没有sum方法
如果我们希望调用内置方法返回的是子类本身的实例,只需要把Symbol.species的返回值改成子类即可:
class MyArray extends Array {
// 返回当前类本身,这样内置方法返回的就是MyArray实例
static get [Symbol.species]() {
return MyArray;
}
sum() {
return this.reduce((acc, cur) => acc + cur, 0);
}
}
const myArr = new MyArray(1, 2, 3);
const sliced = myArr.slice(0, 2);
console.log(sliced instanceof MyArray); // true
console.log(sliced.sum()); // 3,子类方法可以正常使用
Symbol.species在继承内置类型时的作用
Symbol.species的核心作用就是控制内置类型派生对象的构造函数选择,具体体现在以下几个方面:
1. 解决派生对象类型不符合预期的问题
当我们直接继承内置类型时,内置方法默认会使用instanceof检查时找到的第一个构造函数来创建新对象,这往往会导致返回的实例不是我们期望的子类类型。通过Symbol.species可以显式指定返回对象的构造函数,避免类型错乱。
比如继承Map类型时,如果不设置Symbol.species,调用map的filter相关方法返回的可能还是Map实例,而不是我们自定义的子类实例:
class MyMap extends Map {
// 不定义Symbol.species时,默认会使用MyMap作为构造函数吗?实际上内置Map的方法会使用this.constructor[Symbol.species]来获取构造函数,如果没有定义就使用自身的构造函数
// 这里先不定义Symbol.species,看看效果
}
const myMap = new MyMap([['a', 1], ['b', 2]]);
// 假设我们有一个自定义的获取键值对的方法,模拟内置方法返回新实例
const newMap = new myMap.constructor([['c', 3]]);
console.log(newMap instanceof MyMap); // true,默认情况下会使用MyMap作为构造函数
2. 灵活控制返回对象的类型
有时候我们可能希望子类调用父类方法时返回的不是子类本身,而是其他类型,比如返回普通的内置类型实例,这时候就可以通过Symbol.species返回对应的构造函数来实现。比如上面的第一个MyArray示例,我们返回Array,这样调用slice等方法得到的就是普通数组,不需要携带子类的额外方法和属性,更轻量。
3. 兼容内置类型的行为逻辑
内置类型的方法比如Array的map、filter、concat,Map的entries等,在设计时就已经考虑到了Symbol.species的存在,会主动使用它来创建新对象。我们定义Symbol.species之后,不需要修改这些内置方法的实现,就可以让它们返回我们期望类型的对象,完全兼容原有的内置类型行为。
注意事项
- Symbol.species是一个静态的getter,需要定义在类的静态属性中,不能用普通属性的方式定义。
- 如果Symbol.species返回的不是构造函数,或者返回undefined,那么内置方法会使用默认的构造函数来创建对象,通常是当前类的原始内置类型。
- Symbol.species只对内置类型的方法生效,如果你自己定义的子类方法,不会自动使用Symbol.species,需要手动在方法中使用this.constructor[Symbol.species]来获取构造函数创建对象。
下面是一个自定义方法使用Symbol.species的示例:
class MyArray extends Array {
static get [Symbol.species]() {
return MyArray;
}
// 自定义方法,返回新的MyArray实例
customSlice(start, end) {
const Constructor = this.constructor[Symbol.species];
const result = new Constructor();
for (let i = start; i < end && i < this.length; i++) {
result.push(this[i]);
}
return result;
}
}
const myArr = new MyArray(1, 2, 3, 4);
const customSliced = myArr.customSlice(1, 3);
console.log(customSliced instanceof MyArray); // true
console.log(customSliced); // MyArray [2, 3]
总结
Symbol.species是JavaScript中用来控制内置类型派生对象构造函数的知名符号,它可以解决继承内置类型时派生对象类型不符合预期的问题,让我们可以灵活控制内置方法返回的对象类型,同时完全兼容内置类型的原有行为。在实际开发中,如果需要继承Array、Map、Set等内置类型,并且希望这些方法返回的是自定义子类的实例,或者希望返回其他指定类型的实例,就可以通过定义Symbol.species来实现。
Symbol.species派生对象构造函数继承内置类型JavaScript修改时间:2026-06-20 22:42:42