symbol.isconcatspreadable 不参与原型链查找,仅在对象自身属性生效;其属性特性为不可写、不可枚举、不可配置,array.prototype.concat() 仅检查自有属性。

直接说结论:Symbol.isConcatSpreadable 不参与原型链继承查找,它只在对象自身属性上生效;哪怕你把它定义在父类 prototype 上,子实例也不会自动继承该行为。想在复杂继承体系中精准控制,必须显式设置到具体实例或构造函数的原型上,且要注意不可写性。
为什么继承链里 Symbol.isConcatSpreadable 不起作用
Symbol.isConcatSpreadable 的属性特性是 writable: false、enumerable: false、configurable: false —— 这意味着它一旦被定义,就不能被覆盖、不能被 for...in 遍历、也不能通过 delete 删除。更重要的是,Array.prototype.concat() 内部只检查参数对象**自有属性**(own property)是否存在该 symbol 并为 true,它不会沿着 [[Prototype]] 查找。
常见错误现象:
- 把
[Symbol.isConcatSpreadable] = false写在基类BaseArray.prototype上,结果子类实例调用concat()时仍被展开 - 试图用
Object.setPrototypeOf(child, Object.assign({}, parent))“复制” symbol 属性,失败 —— symbol 属性不会被Object.assign()拷贝
在 class 继承中让自定义数组类不被展开
如果你有一个继承自 Array 的类(比如 CustomList extends Array),默认它仍会被 concat() 展开,因为它的 Symbol.isConcatSpreadable 自身值仍是 true(继承自原生 Array 构造逻辑)。要禁用展开,必须在实例化后或构造函数中手动设置:
正确做法:
- 在构造函数末尾加
this[Symbol.isConcatSpreadable] = false - 或在实例创建后立即设置:
const list = new CustomList(1,2,3); list[Symbol.isConcatSpreadable] = false; - 不要尝试在
CustomList.prototype上设置 —— 它对实例无效,concat()不查原型
示例:
class CustomList extends Array {
constructor(...args) {
super(...args);
this[Symbol.isConcatSpreadable] = false; // ✅ 必须设在实例上
}
}
const a = [10, 20];
const b = new CustomList(1, 2, 3);
console.log(a.concat(b)); // [10, 20, [1, 2, 3]] —— 不再打平
让类数组子类支持 concat 展开
如果你封装了一个 DOM 集合代理类或 API 响应包装类(如 ApiResponseList),希望它像数组一样被 concat() 展开,不能依赖“看起来像数组”,必须主动赋予行为:
- 该类实例必须有
length和数字索引属性(如0、1) - 必须在实例自身设置
[Symbol.isConcatSpreadable] = true - 不能靠
Object.setPrototypeOf(instance, Array.prototype)—— 这不会触发 concat 的展开逻辑
示例(安全写法):
class ApiResponseList {
constructor(items) {
items.forEach((item, i) => this[i] = item);
this.length = items.length;
this[Symbol.isConcatSpreadable] = true; // ✅ 显式启用
}
}
const res = new ApiResponseList(['a', 'b']);
console.log([1,2].concat(res)); // [1, 2, 'a', 'b']
最易忽略的一点:symbol 属性无法被 JSON 序列化,也无法被深拷贝工具(如 Lodash cloneDeep)保留。如果你的对象会经过序列化/反序列化、跨 iframe 传递或被某些状态管理库代理,Symbol.isConcatSpreadable 设置会丢失 —— 此时需在反序列化后重新设置,或改用包装函数(如 safeConcat(target, ...sources))做运行时判断和兜底。










