object.assign是双刃剑:它轻便高效但易污染原对象、引发原型污染、绕过setter、造成引用共享;正确用法是object.assign({}, obj1, obj2),避免直接修改源对象。

Object.assign 之所以被称为“双刃剑”,是因为它既轻便高效,又暗藏风险——用对了省事,用错了可能污染原对象、引发原型污染、绕过 setter、或制造难以排查的引用共享问题。
它会直接修改目标对象
很多人误以为 Object.assign 返回的是全新对象,其实它只是把源对象属性“贴”到第一个参数上,并返回这个被修改的目标对象。
- 如果第一个参数是已有对象(比如 this.state 或某个配置项),原数据就被改了,后续逻辑可能出错
- 正确做法:用空对象做 target,Object.assign({}, obj1, obj2),才能真正实现“合并后不伤原主”
- 特别注意数组场景:Object.assign([], arr) 看似复制,但若 arr 元素是对象,新数组里仍是原引用,不是深拷贝
它只做浅拷贝,嵌套结构极易翻车
Object.assign 不递归处理对象内部,遇到嵌套对象或数组,只是复制指针。一个地方改了 info.address.city,另一处跟着变。
- 典型表现:copy = Object.assign({}, original) 后修改 copy.info.name,original.info.name 也变了
- 需要深拷贝时,优先考虑 structuredClone()(现代环境支持循环引用、Date、Map 等)
- 兼容旧环境可选 JSON.parse(JSON.stringify(obj)),但要接受它丢弃 function、undefined、Symbol 和 toJSON() 的副作用
它绕过 setter,破坏响应式或校验逻辑
Vue 2 的 defineProperty、MobX 的 observable、或自定义的 set 拦截,Object.assign 一概不触发——因为它底层是 target[key] = source[key] 这种原始赋值。
- 如果你依赖 setter 做数据转换、日志记录或权限检查,用 assign 就等于跳过了整套机制
- 替代方案:手写 for...in 或 Object.keys 循环,再用 target[key] = value 显式赋值,这样 setter 才会执行
- 框架内建议优先使用其推荐方式,比如 Vue 3 的 reactive({...obj}) 或 Pinia 的 $patch
它可能成为 Prototype Pollution 的入口
当源对象来自不可信输入(如用户提交的 JSON、URL 参数解析结果),且未清洗 key 名,攻击者传入 { "__proto__": { "isAdmin": true } },assign 就会把 isAdmin 写进 Object.prototype。
- 防御关键:在合并前过滤危险键名,如 __proto__、constructor、prototype
- 更彻底的做法(沙箱场景):Object.freeze(Object.prototype),但会全局禁用原型扩展,需评估兼容性
- 别轻信“已升级 lodash 就安全”——CVE-2025-25012 显示,lodash@4.17.15 仍存在绕过,官方基线是 4.17.21











