Symbol.species 是指定内置方法创建新实例所用构造器的内置 symbol;它不改变继承关系,只影响派生对象的构造器,默认未设置时 map/filter 等返回父类实例而非子类实例。
Symbol.species 是一个内置 symbol,用于指定一个构造函数,该构造函数会在内置方法(如 map、filter、slice)内部被用来创建新实例。它不改变类本身的继承关系,只影响“派生出的新对象该用谁来构造”。默认情况下,Array、Promise、RegExp 等都实现了 [@@species],返回自身构造器;但如果你继承了它们并没显式设置 Symbol.species,这些方法就可能返回父类实例而非你的子类实例。
假设你写了一个继承 Array 的类:
class MyArray extends Array { static get [Symbol.species]() { return this; }}
如果删掉那行 static get [Symbol.species],那么:
new MyArray(1, 2, 3).map(x => x * 2) 返回的是 Array 实例,不是 MyArray
instanceof 检查失败:result instanceof MyArray === false
这是因为 Array.prototype.map 内部调用了 this.constructor[Symbol.species],而你没定义,引擎回退到 Array 自身的 [Symbol.species](即 Array),所以用 Array 构造新数组。
关键在返回值必须是**可构造的函数**,且语义要符合预期:
this:每次调用都用当前类(包括子类)构造,最常用也最安全Array:强制所有派生方法返回原生 Array,适合封装类不想暴露子类行为的场景MyArray(硬编码):子类继承后会失效——子类调用 map 仍返回 MyArray,不是它自己所以推荐写法永远是:
static get [Symbol.species]() { return this;}
注意:不能写成箭头函数(无 this 绑定),也不能在实例上定义(必须是静态 getter)。
Promise 和 RegExp 也支持 Symbol.species,但使用逻辑不同:
Promise.prototype.then 会读取 this.constructor[Symbol.species] 来创建新 Promise —— 所以自定义 Promise 类必须设 Symbol.species,否则 then 返回原生 Promise
RegExp.prototype.exec 不用它,但 replace、split 在生成新正则或字符串时可能间接依赖(较隐蔽)Promise 的 Symbol.species 默认就是 Promise,而 Array 的默认是自身,这点一致;但如果你继承 Promise 却不设 Symbol.species,then 返回的就是 Promise,不是你的类真正容易被忽略的是:这个机制只对**内置方法**生效,你自己写的 map 或 then 不会自动读取 Symbol.species —— 必须手动调用 constructor[Symbol.species] 才能复现相同逻辑。