默认情况下 slice 返回原生 array 而非派生类实例,因其内部通过 this.constructor[symbol.species] 确定构造器,未定义时虽 fallback 到 this.constructor,但行为不可靠;正确定义需在派生类中声明 static get [symbol.species]() { return this; }。

默认情况下,slice 返回的不是你的派生类实例,而是原生 Array ——除非你显式设置 Symbol.species。
为什么 slice 不返回你的派生类实例
ES6 数组方法(包括 slice、map、filter、concat 等)内部会读取 this.constructor[Symbol.species] 来决定新数组用哪个构造器创建。未定义时,它 fallback 到 this.constructor;但某些引擎或旧 polyfill 可能行为不一致,尤其在跨框架场景下更不可靠。
常见错误现象:myArr.slice(1) 返回 Array 而非 MyArray,导致后续调用自定义方法(如 sum())报错。
- 仅当派生类没覆写
slice且没定义[Symbol.species]时,才可能“碰巧”返回子类实例(依赖引擎实现,不可靠) -
Array.from()、[...arr]、Array.of()完全不走Symbol.species,它们永远用字面量或传入的构造器 - Vue 3 的
reactive([])或 RxJS 的from()通常绕过Symbol.species,直接返回代理或原生数组
如何正确定义 Symbol.species 让 slice 返回自身类型
必须在派生类中声明一个静态 getter,返回当前类本身(而非 Array),且不能硬编码类名(否则子类继承失效)。
正确写法:
class MyArray extends Array {
static get [Symbol.species]() {
return this;
}
sum() {
return this.reduce((a, b) => a + b, 0);
}
}
关键点:
- 用
this而非MyArray:确保子类(如class SuperArray extends MyArray)调用slice时也返回SuperArray - 必须是
static get,不能是普通属性或方法 - 不要返回
Array——那会强制降级为原生数组,违背“保持类型一致”的目标
slice 以外哪些方法也受 Symbol.species 影响
所有返回新数组实例的内置方法都遵循该协议,包括:
-
map、filter、flatMap、flat -
concat、slice、toReversed(ES2023+)、with(ES2023+) -
splice(仅当有删除项并返回新数组时)
但以下操作完全不查 Symbol.species:
-
push、pop、shift、unshift(就地修改) -
Array.from()、Array.of()、扩展运算符[...arr] -
JSON.parse()或任何手动new MyArray()构造
多层继承和第三方库中的实际陷阱
即使你定义了 [Symbol.species],仍可能在真实项目中掉坑:
- Lodash 的
_.filter(arr)不调用arr.filter(),也不查Symbol.species,返回的是 plainArray - 从 Vue 的
ref<myarray></myarray>解构出的数组,其filter实际调用的是 Proxy 包裹的原生数组方法,Symbol.species失效 - 如果某处代码写了
return arr.slice().map(...),而arr是MyArray实例,但中间某步被转成Array,后续链式调用就断了类型
补救建议(仅限关键路径):
function ensureMyArray(arr) {
if (!(arr instanceof MyArray)) {
return new MyArray(arr);
}
return arr;
}
真正复杂的地方不在定义 Symbol.species,而在于你无法控制所有调用方——只要有一处用了不走该协议的 API 或库函数,类型就断了。别指望它 100% 全局生效,只把它当作“自有方法链内类型延续”的保障机制。










