JavaScript中对象引用赋值为何修改原对象?如何避免?
理解JavaScript对象引用与避免修改原对象的方法
嘿,这个问题其实戳中了JavaScript里引用类型的核心特性,咱们一步步拆解清楚~
首先先看你的代码示例(已经格式化好啦):
const config = {a: {x: 1, y: 2, z: 3}, b: {x: 12, y: 23, z: 34}}; console.log(config); // -> // [object Object] { // a: [object Object] { // x: 1, // y: 2, // z: 3 // }, // b: [object Object] { // x: 12, // y: 23, // z: 34 // } // } const items = []; items.push(config['a']); console.log(items); // -> // [[object Object] { // x: 1, // y: 2, // z: 3 // }] items[items.length - 1].q = "mutation"; console.log(items); // -> // [[object Object] { // q: "mutation", // x: 1, // y: 2, // z: 3 // }] console.log(config); // -> // [object Object] { // a: [object Object] { // q: "mutation", // x: 1, // y: 2, // z: 3 // }, // b: [object Object] { // x: 12, // y: 23, // z: 34 // } // } console.log(config.a) // -> // [object Object] { // q: "mutation", // x: 1, // y: 2, // z: 3 // }
为什么JavaScript会这样设计?
你猜的没错——items[0]是config.a的引用,而不是副本。这是因为JavaScript里的对象(包括数组、普通对象、函数等)属于引用类型,和数字、字符串这类基本类型的存储逻辑完全不同:
- 基本类型的值直接存在栈内存里,赋值时会复制整个值,两个变量互不影响;
- 引用类型的值存在堆内存里,变量保存的只是指向堆内存的“地址”(也就是引用)。当你把
config.a放进数组时,只是把这个地址存进了数组,所以items[0]和config.a指向的是同一个堆内存对象,修改任何一个都会同步影响另一个。
这种设计本质是为了性能优化:如果每次赋值对象都复制整个对象(尤其是大对象),会占用大量内存,运行速度也会变慢。而引用的方式只传递地址,效率高得多。同时,很多场景下我们也需要这种“共享数据”的特性——比如多个组件操作同一个状态集合,不用手动同步就能保持数据一致。
如何避免修改原config对象?
核心思路是:创建原对象的副本,把副本放进数组,而不是直接放引用。根据对象的嵌套程度,分为两种情况:
1. 浅拷贝(适合单层结构的对象)
如果你的对象没有嵌套(比如config.a里都是基本类型值),用浅拷贝就足够了:
- 用展开运算符:
items.push({...config['a']}); - 用
Object.assign():items.push(Object.assign({}, config['a']));
2. 深拷贝(适合多层嵌套的对象)
如果对象有嵌套结构(比如config.a里还有子对象),浅拷贝只会复制外层对象的引用,嵌套的对象还是会共享。这时候需要深拷贝:
- 最简单的方法:
JSON.parse(JSON.stringify())
这个方法能快速实现深拷贝,但有局限性:不能拷贝函数、Symbol、undefined,遇到循环引用的对象会报错。items.push(JSON.parse(JSON.stringify(config['a']))); - 手写递归深拷贝函数(处理复杂场景):
如果需要支持函数、Symbol等特殊类型,可以自己写一个递归函数:function deepClone(obj) { // 处理基本类型和null if (obj === null || typeof obj !== 'object') return obj; // 处理Date对象 if (obj instanceof Date) return new Date(obj); // 处理数组 if (obj instanceof Array) return obj.map(item => deepClone(item)); // 处理普通对象 const clonedObj = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { clonedObj[key] = deepClone(obj[key]); } } return clonedObj; } // 使用方式 items.push(deepClone(config['a']));
修改后的代码示例(用深拷贝):
const config = {a: {x: 1, y: 2, z: 3}, b: {x: 12, y: 23, z: 34}}; console.log(config); const items = []; // 放入深拷贝的副本 items.push(JSON.parse(JSON.stringify(config['a']))); console.log(items); items[items.length - 1].q = "mutation"; console.log(items); // 副本被修改 console.log(config); // 原config完全不受影响! console.log(config.a); // 原对象保持初始状态
内容的提问来源于stack exchange,提问作者sudo bangbang
相关产品推荐
相关产品推荐

