Object.assign仅拷贝自身可枚举属性,忽略原型链上所有属性(含方法、访问器、不可枚举属性),导致继承断裂;正确做法是分步提取全链自有属性、合并、再重建原型关系。

Object.assign 本身不读取原型链上的任何属性,所以它根本不会“误覆盖”原型方法——它压根就看不见。所谓“覆盖共有方法”,其实是开发者误以为它会合并原型属性,结果只拿到了实例自身的属性,导致原型上的方法、访问器、计算属性全部丢失,继承关系彻底断裂。
原型属性天然被 Object.assign 忽略
这是设计行为,不是 bug。Object.assign 只遍历 自身可枚举属性(等同于 Object.keys(obj) 的结果),对 __proto__ 或 Object.getPrototypeOf(obj) 上的属性完全无感。
- 例如:
const child = Object.create({ say() { return 'hi'; } }); child.name = 'A'; Object.assign({}, child)→ 得到{ name: 'A' },say方法消失 - 即使原型上有
get fullName(),也不会被拷贝;不可枚举的constructor同样被跳过
真正的问题是:你本不该用 Object.assign 处理继承结构
如果你的目标是“保留原型链 + 合并实例状态”,那 Object.assign 单独使用永远做不到。它产出的是一个普通 Object 实例,没有原始 constructor,instanceof 判定失败,所有原型方法一并清零。
- 错误做法:
const merged = Object.assign({}, instance)→ 扁平化、断链、失方法 - 正确方向:先提取整条原型链上的自有属性(含多级),再合并;之后手动重建原型关系
安全合并的三步落地法
想让实例状态和原型能力都不丢,得绕开 Object.assign 的限制,分步操作:
-
提取全链自有属性:用
Object.getOwnPropertyNames()配合Object.getPrototypeOf()逐层向上收集,过滤掉constructor等非数据属性 -
按需合并属性值:用
Object.assign({}, ...allPropsObjects)合并各层属性,注意后层(更靠近实例)同名属性自然覆盖前层(符合继承逻辑) -
重建原型链:用
Object.setPrototypeOf(result, Object.getPrototypeOf(original))把合并后的对象挂回原原型
更务实的替代方案
多数场景下,你并不真需要“带原型的合并”,而是想安全地提取或初始化状态:
- 仅导出当前实例快照 → 直接用
{...instance}或Object.assign({}, instance),明确接受“无原型”结果 - 需完整克隆(含原型+状态)→ 用
Object.assign(Object.create(Object.getPrototypeOf(obj)), obj) - 配置合并且含嵌套 → 放弃 Object.assign,改用
structuredClone+ 手动递归合并,或引入lodash.merge










