object.assign和展开运算符只复制对象自身的可枚举属性,不复制原型链及不可枚举属性;因二者均基于object.keys()或[[enumerate]]机制,仅遍历自有可枚举属性,故原型属性和不可枚举属性均被忽略。

Object.assign 和展开运算符({...obj})都只复制对象自身的可枚举属性,**不会复制原型链上的属性**,这是它们的共同行为,也是 JavaScript 原型机制的自然体现。
为什么原型链属性被忽略
JavaScript 中的属性访问是“读时继承”,但属性复制是“写时自有”。Object.assign 和展开运算符遍历的是目标对象的 自身可枚举属性(即通过 Object.keys() 能拿到的属性),而 Object.keys() 本身就不包含原型链上的属性。原型上的方法或数据只是被“访问到”,并非对象“拥有”它。
例如:
const proto = { foo: 'from proto', method() { return 'proto method'; } };
const obj = Object.create(proto);
obj.bar = 'own prop';
console.log(obj.foo); // 'from proto' —— 可读
console.log(Object.keys(obj)); // ['bar'] —— 仅自身属性
console.log({ ...obj }); // { bar: 'own prop' }
console.log(Object.assign({}, obj)); // { bar: 'own prop' }
如何显式复制原型链上的属性
如果确实需要把原型链上可枚举的属性也合并进来,需手动遍历原型链。可用 Object.getOwnPropertyNames() 配合 Object.getPrototypeOf() 逐级提取,再过滤出可枚举属性(或按需保留不可枚举的)。
- 用
for...in可遍历包括原型链在内的所有可枚举属性(但会跳过不可枚举属性) - 用
Object.getOwnPropertyNames()+Object.getPrototypeOf()循环可获取所有自有属性(含不可枚举),再结合Object.prototype.propertyIsEnumerable判断是否可枚举 - 简单场景下,
Object.assign({}, obj, ...Object.getPrototypeOf(obj) ? [Object.getPrototypeOf(obj)] : [])仅复制直接原型(不递归),且仅限其自身可枚举属性
不可枚举属性同样被忽略
无论是自身还是原型上的不可枚举属性(如 Object.prototype.toString),都不会被 Object.assign 或展开运算符复制——因为二者底层都依赖 [[Enumerate]] 或等价机制,而不可枚举属性不在该枚举范围内。
验证示例:
const obj = {};
Object.defineProperty(obj, 'hidden', {
value: 'secret',
enumerable: false
});
console.log({ ...obj }); // {}
console.log(Object.assign({}, obj)); // {}
实际开发中的建议
多数情况下,忽略原型链属性是合理且预期的行为:浅拷贝应只关注对象“自己声明的数据”,而非继承来的接口或默认行为。若业务逻辑依赖原型属性参与合并,说明设计可能混淆了“数据”与“行为”的边界。
- 优先用类或构造函数明确区分实例数据和共享方法
- 需要合并配置时,确保配置对象是纯数据对象(plain object),避免意外继承
- 如需深度继承式合并(如主题配置叠加),应自行实现递归合并逻辑,而不是依赖浅拷贝原语
不复杂但容易忽略。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











