js Object.assign合并对象
别再误用 Object.assign 了,合并对象前的这几点坑得先避
做前端开发久了,处理配置项或者用户信息合并是家常便饭。以前大家习惯用 Object.assign() 来搞定,代码写起来确实顺手。但最近 review 代码时,发现不少同事还在“无脑”套用它,结果埋下了不少隐患。今天咱们就抛开枯燥的文档定义,聊聊在实际项目中,怎么用这个 API 才不踩雷。
它是“原地修改”,而不是“生成新值”
很多人第一次接触 Object.assign(target, ...sources) 时,默认它像数学加法一样返回一个新结果。实际上,它直接修改并返回第一个参数(target)。
const defaults = { theme: 'light', lang: 'zh' };
const userOpts = { theme: 'dark' };
// ❌ 常见误区:认为 defaults 还是原样
const finalConfig = Object.assign(defaults, userOpts);
console.log(defaults.theme); // dark!defaults 被污染了
console.log(finalConfig === defaults); // true
如果你在意数据不可变性,比如在使用 Redux 或 Vue 响应式状态时,这种写法会导致副作用。正确的做法是传入一个空对象作为第一个参数。虽然这样会创建新内存,但在维护性和调试上能避免很多玄学问题。
const finalConfig = Object.assign({}, defaults, userOpts);
浅拷贝带来的嵌套陷阱
这是最容易被忽视的逻辑漏洞。Object.assign 只进行一层浅拷贝。当你的源对象里包含嵌套对象时,修改合并后的深层属性,会反过来影响原始数据。
const base = { style: { color: 'red', size: 12 } };
const update = { style: { color: 'blue' } };
const merged = Object.assign({}, base, update);
merged.style.size = 20;
// ⚠️ base.style.size 也会变成 20,如果底层还依赖 base 的话
这种情况下,简单的合并已经不够用了。如果必须处理深层结构,要么手写递归深度合并逻辑,要么引入成熟的 lodash 库,不要试图用多次嵌套的 Object.assign 去硬解,那样可读性简直灾难。
一些你可能不知道的“过滤机制”
除了深浅拷贝,Object.assign 还有个隐形规则:它只会复制可枚举的自有属性。这意味着原型链上的属性、不可枚举属性(enumerable: false)都会被忽略。
更有趣的是,如果遇到访问器属性(getter/setter),它会执行 setter 方法并将值赋值给目标对象,而不是直接把 getter/setter 描述符复制过去。
const source = {
get name() { return 'Guest'; }
};
const target = {};
Object.assign(target, source);
// target.name 不再是访问器,而是一个普通字符串 'Guest'
console.log(Object.getOwnPropertyDescriptor(target, 'name').get); // undefined
如果你的业务逻辑依赖对象的访问器特性,直接用 Object.defineProperty 或者展开运算符可能比 Object.assign 更可控。
什么时候选展开运算符 ...?
现在大部分新项目都倾向于用扩展运算符 { ...obj } 来做合并。它的行为与 Object.assign 基本一致,也是浅拷贝且不会改变原对象(除非你手动赋值回去)。
两者的核心差异在于意图表达和语法糖:
- 可读性:
{ ...a, ...b }更符合现代 ES6+ 风格,阅读成本更低,一眼就能看出是在构建新对象。 - 安全性:
Object.assign(null, obj)会报错,而{ ...null }是合法的(结果为空对象),这在某些防御性编程中反而更安全。 - 性能:在简单合并场景下,两者性能差异微乎其微,不用过度纠结。
但别忘了,原生方法的兼容性更好。如果项目要支持 IE11 以下环境,Object.assign 配合 Polyfill 依然是稳妥的选择。
写在最后
工具没有绝对的好坏,只有适不适合场景。Object.assign 依然强大,特别是在需要显式 mutate 某个目标对象以提升性能的特定库开发中。但对于日常的业务开发,优先保证数据流清晰和无副作用。
下次准备合并对象前,不妨花两秒确认一下:你是想修改原对象,还是生成新配置?有没有深层嵌套的结构?确认好这两点,再决定是用 assign 还是展开符,代码的健壮性自然就上去了。


还没有评论,来说两句吧...