Object.assign只拷贝对象自身的可枚举属性,不处理原型链上的继承属性,因其行为规范明确限定于own enumerable properties;需手动遍历原型链才能合并原型属性。

Object.assign 不会拷贝原型链上的属性,它只处理对象自身的可枚举属性。
为什么原型链属性不会被拷贝
Object.assign 的行为规范明确指出:它只会遍历并复制目标对象(第一个参数)和源对象(后续参数)的自身可枚举属性(own enumerable properties)。原型链上的属性属于继承属性,不可枚举(除非手动设为 enumerable: true),因此被自动忽略。
例如:
const parent = { name: 'Parent' };
Object.prototype.age = 30; // 添加到原型链(不推荐,仅作演示)
const child = Object.create(parent);
child.id = 123;
console.log(child.name); // 'Parent'(继承自原型)
console.log(child.age); // 30(继承自 Object.prototype)
const merged = Object.assign({}, child);
console.log(merged); // { id: 123 } —— name 和 age 都没出现
如何让原型属性参与拷贝(需主动提取)
如果确实需要把原型链上某些属性也合并进去,必须手动获取并合并。常用方法是结合 Object.getPrototypeOf 和 Object.getOwnPropertyNames(或 Object.keys + 递归遍历原型链)。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
- 用 Object.getOwnPropertyNames(obj) 获取对象自身所有属性名(含不可枚举的)
- 用 Object.getPrototypeOf(obj) 向上遍历原型链
- 逐层收集属性,并用 Object.getOwnPropertyDescriptor 判断是否可枚举、是否应拷贝(注意避免重复、防止死循环)
简易示例(仅处理一层原型,且只拷贝可枚举属性):
function assignWithPrototype(target, source) {
const allProps = new Set([...Object.keys(source), ...Object.keys(Object.getPrototypeOf(source) || {})]);
allProps.forEach(key => {
if (source.propertyIsEnumerable(key)) {
target[key] = source[key];
}
});
return target;
}
const parent = { type: 'animal' };
const child = Object.create(parent);
child.name = 'dog';
assignWithPrototype({}, child); // { name: 'dog', type: 'animal' }
更安全的做法:明确意图,避免依赖原型属性拷贝
原型链设计本意是共享行为,而非数据继承。把关键配置或数据放在实例自身更可靠。
- 优先用 构造函数 / class 实例初始化 把必要属性赋给 this
- 若需“模板式”合并,用纯对象字面量或 Object.create(null) 避免原型干扰
- 深拷贝场景(如结构复杂)直接选用 structuredClone(现代环境)或成熟库(lodash.cloneDeep),它们也不处理原型属性,但语义更清晰
小结:别指望 Object.assign 做原型的事
它不是遗漏,而是刻意设计——只操作实例自身状态。想让原型属性参与,就得自己走一遍原型链;但大多数业务场景下,这恰恰说明模型设计可以更扁平、更明确。不复杂但容易忽略。

















