object.assign() 的同名属性覆盖是设计行为而非缺陷,按参数顺序线性覆盖可枚举自有属性,不涉及原型、不可枚举属性或 symbol 键;需深层合并时应换用其他策略。

JavaScript 中浅拷贝本身不“导致”覆盖,而是 Object.assign() 这类合并操作在执行浅拷贝时,按参数顺序逐个赋值,天然遵循“后写覆盖前写”规则。所谓“同名覆盖”,是属性复制过程中的确定性行为,不是意外,也不是浅拷贝的缺陷——它就是设计如此。
覆盖发生在属性赋值阶段,不是拷贝深度决定的
浅拷贝只说明嵌套对象的引用被复制(而非递归复制内容),但覆盖逻辑独立于此:
- 只要两个源对象都有
name属性,后出现的那个会直接把前一个的值替换成自己的值; - 这个替换发生在目标对象的
name属性上,不管它是字符串、数字,还是一个嵌套对象的引用; - 例如:
Object.assign({}, {a: 1, b: {x: 1}}, {b: {y: 2}})→{a: 1, b: {y: 2}},整个b属性被替换,不是“合并x和y”。
多个源对象合并时,顺序决定覆盖优先级
传入 Object.assign 的源对象从左到右依次处理,靠右的源对象拥有更高优先级:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
-
Object.assign(target, defaults, overrides):overrides覆盖defaults,适合配置合并; -
Object.assign(target, mixinA, mixinB):若mixinB有同名方法,它会生效,mixinA的同名方法被屏蔽; - 没有“保留左侧未定义字段 + 右侧新增字段”的智能合并,只有线性覆盖。
注意:原型属性、不可枚举属性、Symbol 键都不会参与覆盖
覆盖只作用于可枚举的自有属性,这意味着:
- 继承来的
toString或constructor不会被复制,更不会被覆盖; -
Object.defineProperty(obj, 'hidden', { enumerable: false, value: 42 })中的hidden不会被Object.assign处理; - Symbol 键需显式提取(如
Object.getOwnPropertySymbols())并手动合并,否则忽略。
想避免覆盖?得换策略,不是改浅拷贝
如果需要“深层合并”或“键级合并”(比如两个对象都有 api,希望把它们的子字段合起来),Object.assign 不适用,应考虑:
- 手写递归合并函数;
- 使用
structuredClone()(仅限可序列化值)配合自定义逻辑; - 引入成熟工具如 Lodash 的
merge(); - 用
Map或Set管理配置键,再生成最终对象。
不复杂但容易忽略:覆盖不是 bug,是 Object.assign 的明确契约。理解它,才能用对场景。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










