为何对象方法会改写原对象?修改嵌套对象属性为何同步原对象?
嘿,这个问题问得特别典型——这其实是JavaScript里引用类型的核心特性在搞事情,不少刚接触JS的开发者都会在这里踩坑。咱们一步步拆解来看:
先搞懂你的代码示例里发生了什么
先看你给出的代码:
const z = { foo: 'bar' }; const s = z; s.foo = 'baz'; s == z // true
在JavaScript里,对象(包括数组、函数这类)属于引用类型,和字符串、数字这种「值类型」完全不一样。当你执行const s = z的时候,你并没有把z这个对象完整复制一份给s——你只是把z指向这个对象的内存地址复制给了s。
换句话说,z和s现在就像同一个房子的两把钥匙,它们都指向堆内存里的同一个对象。不管你用哪把钥匙(哪个变量)去修改房子里的东西(对象属性),本质上都是在改同一个房子里的内容。所以你修改s.foo之后,z.foo自然也跟着变了,最后s == z返回true也理所当然——因为它们本来就是同一个对象的不同别名而已。
嵌套对象的情况也是同一个道理
如果是嵌套对象,比如const z = { foo: { bar: 'baz' } }; const s = z;,当你修改s.foo.bar = 'qux'时,z.foo.bar也会跟着变。这是因为s.foo和z.foo同样指向的是嵌套对象{ bar: 'baz' }的内存地址,只要是引用类型的赋值,传递的都是内存地址,不是实际的对象内容。
如何避免原对象被改写?
要解决这个问题,核心就是创建原对象的真正拷贝,而不是只复制引用。根据对象的结构,我们可以选择浅拷贝或深拷贝:
1. 浅拷贝(适合无嵌套的单层对象)
如果你的对象只有一层(没有嵌套的对象/数组),用这些方法就能创建一个独立的副本:
- 对象展开运算符:
const z = { foo: 'bar' }; const s = {...z}; // 复制整个对象的第一层属性 s.foo = 'baz'; console.log(z.foo); // 输出 'bar',原对象不受影响 console.log(s == z); // false,现在是两个不同的对象 Object.assign()方法:const s = Object.assign({}, z);
2. 深拷贝(适合有嵌套结构的对象)
如果对象里还有嵌套的对象或数组,浅拷贝就不够用了——因为浅拷贝只会复制第一层的引用,嵌套的部分还是共享内存。这时候需要用深拷贝:
- JSON序列化/反序列化:最简单的方法,但有局限性(不能复制函数、Symbol、undefined,循环引用的对象会报错):
const z = { foo: { bar: 'baz' } }; const s = JSON.parse(JSON.stringify(z)); s.foo.bar = 'qux'; console.log(z.foo.bar); // 输出 'baz',原对象没被修改 - 手写递归深拷贝函数:自己写一个函数遍历对象的每一个属性,遇到引用类型就递归复制,值类型直接复制(适合需要自定义拷贝逻辑的场景)。
- 第三方工具方法:比如Lodash的
_.cloneDeep(),能处理大部分复杂的深拷贝场景。
总结一下
本质原因就是:JavaScript的引用类型赋值是「传引用」(传递内存地址),而不是「传值」。要修改副本不影响原对象,就得创建真正的拷贝,根据对象结构选择浅拷贝或深拷贝就好啦。
内容的提问来源于stack exchange,提问作者Max Wolfen

