浅拷贝只复制对象第一层属性,嵌套引用类型仍共享内存地址;object.assign、展开运算符等均属此类,修改深层属性会意外影响原对象。

浅拷贝在 JS 中看似简单,实则极易踩坑——表面复制了对象,修改嵌套属性时却意外改到了原数据。掌握它的关键不是背方法,而是理解“哪一层被复制、哪一层仍共享”,再配合针对性的调试手段。
陷阱一:只看第一层,忽略深层引用
几乎所有原生浅拷贝方法(Object.assign()、{...obj}、Array.from()、slice() 等)都只处理对象或数组的第一层。只要某项是对象、数组、日期、正则等引用类型,拷贝后的新旧变量仍指向同一块堆内存。
- 例如:
const user = { name: 'Alice', config: { theme: 'dark' } };,用{...user}拷贝后,copy.config.theme = 'light'会同步改变user.config.theme - 数组同理:
const list = [1, 2, { id: 1 }]; const copy = [...list]; copy[2].id = 99;→list[2].id也变成 99
陷阱二:忽略不可枚举属性与原型链
Object.assign() 和展开运算符只遍历对象自身的可枚举属性,不复制:
- 不可枚举属性(如 Object.defineProperty(obj, 'hidden', { value: 42, enumerable: false }))
- 原型上的属性或方法(如从 class A { method() {} } 实例拷贝后,method 就丢了)
- getter/setter(拷贝后变成普通属性值,响应式逻辑失效)
这在 Vue 或 React 的 class 组件、带计算属性的对象中特别容易出问题。
调试技巧:三步快速定位是否被浅拷贝坑了
遇到“改了新对象,原对象也变了”,按顺序检查:
-
查修改位置:确认你改的是第几层。如果改的是
obj.a.b.c这种三级结构,那大概率是浅拷贝没生效 -
查引用一致性:用
console.log(copy.nested === original.nested)。若输出true,说明该属性仍是同一引用 -
查构造来源:回溯拷贝代码——是否用了
Object.assign或展开符?有没有对嵌套对象做二次处理?比如{ ...user, config: { ...user.config } }才能切断第二层引用
实用建议:什么情况下必须手动深拷贝
不必一概而论“全用深拷贝”,但以下场景要格外警惕浅拷贝:
- Redux / Zustand / Jotai 等状态管理中更新嵌套 state
- React 中传递 props 给子组件并可能在子组件内修改(尤其表单控件)
- 配置对象被多个模块复用(如 API 请求默认 headers、图表 options)
- 函数参数接收对象后需内部修改且不能影响调用方传入的数据











