修改 JavaScript 对象的副本会导致原始对象发生更改

Modifying a copy of a JavaScript object is causing the original object to change

提问人:Vallabha 提问时间:3/14/2015 最后编辑:Vallabha 更新时间:1/5/2023 访问量:117633

问:

我正在复制到objAobjB

const objA = { prop: 1 }, 
const objB = objA; 
objB.prop = 2;
console.log(objA.prop); // logs 2 instead of 1

数组也有同样的问题

const arrA = [1, 2, 3], 
const arrB = arrA; 
arrB.push(4); 
console.log(arrA.length); // `arrA` has 4 elements instead of 3.
JavaScript 对象 复制

评论

0赞 tomek550 3/14/2015
如果您不使用 jQuery,这可能会有所帮助:stackoverflow.com/questions/728360/...

答:

6赞 guest271314 3/14/2015 #1

尝试使用 $.extend():

但是,如果您想保留这两个原始对象,则可以 可以通过将空对象作为目标传递来做到这一点:

var object = $.extend({}, object1, object2);


var tempMyObj = $.extend({}, myObj);

评论

0赞 Rohit416 3/14/2015
宾果游戏,这就是我要写的。:)
7赞 Rory O'Kane 4/21/2017
JavaScript 现在内置了一个类似的函数 Object.assign()。用法示例:.Object.assign({}, myObj)
0赞 JJ Ward 1/10/2022
@RoryO'Kane - 这给了我我需要的东西,非常感谢!
167赞 robbmj 3/14/2015 #2

很明显,您对该语句的作用有一些误解。var tempMyObj = myObj;

在 JavaScript 中,对象是通过引用(更准确地说是引用的值)传递和分配的,因此 和 都是对同一对象的引用。tempMyObjmyObj

下面是一个简化的插图,可以帮助您直观地了解正在发生的事情

// [Object1]<--------- myObj

var tempMyObj = myObj;

// [Object1]<--------- myObj
//         ^ 
//         |
//         ----------- tempMyObj

正如您在赋值后看到的,两个引用都指向同一个对象。

如果需要修改一个副本而不是另一个副本,则需要创建一个副本

// [Object1]<--------- myObj

const tempMyObj = Object.assign({}, myObj);

// [Object1]<--------- myObj
// [Object2]<--------- tempMyObj

旧答案:

以下是创建对象副本的其他几种方法

由于您已经在使用 jQuery:

var newObject = jQuery.extend(true, {}, myObj);

使用原版 JavaScript

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var newObject = clone(myObj);

查看 此处此处

评论

1赞 robbmj 3/14/2015
@JAAulde,感谢您在更新问题后更新答案。我不确定我是否能抓住这一点。
2赞 JAAulde 3/14/2015
没关系!当我对一个已经回答的问题进行如此广泛的编辑时,我会尝试跟进。:)
1赞 PotatoFarmer 11/13/2019
值得注意的是,当您创建自己的对象时,这似乎开始抬头(例如 ).我猜很多新手(像我一样)不会注意到这一点,因为许多命令为我们输出一个新对象(例如 map/filter/etc)。我很难理解为什么有人会理智地想要目前的行为......let a={}; let b=a; b.blah=1;console.log(a)
4赞 Victor Cui 3/16/2021
Object.assign({}, myObj);没有为我创建副本。反而起作用了。var tempMyObj = Object.create(myObj);
0赞 Sebastian Simon 7/24/2021
这个答案应该包括如何使用数组来执行此操作:、 或 。const arrB = arrA.slice();const arrB = Array.from(arrA);const arrB = [ ...arrA ];
7赞 Sandeep Mukherji 8/10/2018 #3

尝试使用 create() 方法,如下所述。

var tempMyObj = Object.create(myObj);

这将解决问题。

评论

1赞 Kartik Chauhan 4/17/2019
虽然你的答案是正确的,绝对适合这里。但是使用 Object.create() 有一个缺点。它将属性添加到对象的 dunder proto _proto_ 中,而不是对象。Object.assign() 将属性添加到对象中。
2赞 Marcio 9/2/2018 #4

这可能非常棘手,让我试着用一种简单的方式表达它。当你在javascript中将一个变量“复制”到另一个变量时,你实际上并不是在将它的值从一个变量复制到另一个变量,而是在为复制的变量分配一个对原始对象的引用。要实际制作副本,您需要创建一个新对象使用

棘手的部分是因为为复制的变量分配新值和修改其值之间存在差异。当您为 copy 变量赋值新值时,您将摆脱引用并将新值赋给副本,但是,如果只修改副本的值(不赋值),则您正在修改副本和原始

希望这个例子对你有所帮助!

let original = "Apple";
let copy1 = copy2 = original;
copy1 = "Banana";
copy2 = "John";

console.log("ASSIGNING a new value to a copied variable only changes the copy. The ogirinal variable doesn't change");
console.log(original); // Apple
console.log(copy1); // Banana
console.log(copy2); // John 

//----------------------------

original = { "fruit" : "Apple" };
copy1 = copy2 = original;
copy1 = {"animal" : "Dog"};
copy2 = "John";

console.log("\n ASSIGNING a new value to a copied variable only changes the copy. The ogirinal variable doesn't change");
console.log(original); //{ fruit: 'Apple' }
console.log(copy1); // { animal: 'Dog' }
console.log(copy2); // John */

//----------------------------
// HERE'S THE TRICK!!!!!!!

original = { "fruit" : "Apple" };
let real_copy = {};
Object.assign(real_copy, original);
copy1 = copy2 = original;
copy1["fruit"] = "Banana"; // we're not assiging a new value to the variable, we're only MODIFYING it, so it changes the copy and the original!!!!
copy2 = "John";


console.log("\n MODIFY the variable without assigning a new value to it, also changes the original variable")
console.log(original); //{ fruit: 'Banana' } <====== Ops!!!!!!
console.log(copy1); // { fruit: 'Banana' }
console.log(copy2); // John 
console.log(real_copy); // { fruit: 'Apple' } <======== real copy!

31赞 Ahmed El Damasy 9/26/2019 #5

使用 JSON.parse() 和 JSON.stringify 的深度克隆对象

// Deep Clone
obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));

参考文献:本文

更好的参考这篇文章

评论

0赞 Mina Ragaie 3/1/2022
这个很棒,但它不会克隆对象内部的函数
0赞 Ahmed El Damasy 3/2/2022
克隆包含多级或包含函数的对象是一个复杂的过程,您应该使用 Lodash(lodash.com)库来帮助您完成此过程或制作自己的算法。
2赞 ayu.sheee 1/7/2021 #6

如果您在数组方面遇到同样的问题,那么这里是解决方案

let sectionlist = [{"name":"xyz"},{"name":"abc"}];
let mainsectionlist = [];
for (let i = 0; i < sectionlist.length; i++) {
     mainsectionlist[i] = Object.assign({}, sectionlist[i]);
}
7赞 Yusuf Febrian 4/2/2021 #7

使用三个点在新变量中展开对象

const a = {b: 1, c: 0};
let d = {...a};

评论

1赞 Raphaël Balet 4/2/2021
云你开发一点点你的答案来解释为什么点差算子也在工作?它可能有助于其他人理解您的答案
0赞 Anas 9/11/2023
这将仅复制对象的顶级属性,但如果对象具有嵌套对象,则仍将引用原始嵌套对象,因此此方法不会复制嵌套对象,它将仅包含原始嵌套对象。
-3赞 Vinoth Natrajan 4/20/2021 #8

将原始对象序列化为 JSON,并反序列化为另一个相同类型的对象变量。这将为您提供包含所有属性值的对象副本。对原始对象的任何修改都不会影响复制的对象。

string s = Serialize(object); //Serialize to JSON
//Deserialize to original object type
tempSearchRequest = JsonConvert.DeserializeObject<OriginalObjectType>(s);

评论

4赞 Alex D 4/20/2021
这是一个特定于 JavaScript 的问题,与 JavaScript 如何处理引用有关,为什么您的代码示例是 .NET?
37赞 jnaklaas 7/6/2021 #9

总而言之,为了澄清起见,有四种方法可以复制 JS 对象。

  1. 普通副本。更改原始对象的属性时,复制对象的属性也会更改(反之亦然)。
const a = { x: 0}
const b = a;
b.x = 1; // also updates a.x
  1. 一个浅拷贝。对于原始对象和复制对象,顶级属性是唯一的。不过,嵌套属性将在两个对象之间共享。使用点差运算符或 ....{}Object.assign()
const a = { x: 0, y: { z: 0 } };
const b = {...a}; // or const b = Object.assign({}, a);

b.x = 1; // doesn't update a.x
b.y.z = 1; // also updates a.y.z
  1. 深度副本。对于原始对象和复制对象,所有属性都是唯一的,甚至包括嵌套属性。对于深度复制,请将对象序列化为 JSON,然后将其解析回 JS 对象。
const a = { x: 0, y: { z: 0 } };
const b = JSON.parse(JSON.stringify(a)); 

b.y.z = 1; // doesn't update a.y.z
  1. 一个完整的深度副本。使用上述技术,将丢弃在 JSON 中无效的属性值(如函数)。如果您需要深层复制并保留包含函数的嵌套属性,您可能需要查看一个实用程序库,例如 .lodash
import { cloneDeep } from "lodash"; 
const a = { x: 0, y: { z: (a, b) => a + b } };
const b = cloneDeep(a);

console.log(b.y.z(1, 2)); // returns 3
  1. using 确实会创建一个新对象。属性在对象之间共享(更改一个属性也会更改另一个属性)。与普通副本的区别在于,属性被添加到新对象的原型下。当您从不更改原始对象时,这也可以用作浅拷贝,但我建议使用上述方法之一,除非您特别需要此行为。Object.create()__proto__

评论

4赞 Quentin 1/25/2022
请注意,用于深度复制对象将丢弃任何在 JSON 中无效的值(如函数)和任何不可枚举的值。JSON.stringify
1赞 haventchecked 10/21/2022
感谢您指出这是一个浅层副本,不会制作嵌套对象的唯一副本。我遇到了这个问题,但不确定为什么我的原始值被修改了!...{}
4赞 Andrea Giammarchi 3/10/2022 #10

由于我在浅层复制/克隆情况的建议答案中找不到此代码,因此我将在这里保留此代码

// shortcuts
const {
  create,
  getOwnPropertyDescriptors,
  getPrototypeOf
} = Object;

// utility
const shallowClone = source => create(
  getPrototypeOf(source),
  getOwnPropertyDescriptors(source)
);

// ... everyday code ...

const first = {
  _counts: 0,
  get count() {
    return ++this._counts;
  }
};

first.count;  // 1

const second = shallowClone(first);

// all accessors are preserved
second.count; // 2
second.count; // 3
second.count; // 4

// but `first` is still where it was
first.count;  // just 2

与 或 操作相比,主要区别在于此实用程序将在此过程中保留所有访问器、符号等,包括继承。Object.assign{...spread}

这个领域的所有其他解决方案似乎都忽略了这样一个事实,即克隆甚至复制不仅仅是检索一次的属性值,而且在日常情况下,访问器和继承可能更受欢迎。

对于其他所有内容,请使用本机方法或其 polyfill 👋structuredClone

0赞 Vaibhavbuccha 3/28/2022 #11

在 Javascript 中,对象作为引用传递,它们使用浅层比较,因此当我们更改对象的任何实例时,相同的更改也会引用到主对象。

要忽略此复制,我们可以将 JSON 对象字符串化。

例:-

let obj = {
   key: "value"
}
function convertObj(obj){
   let newObj = JSON.parse(obj);
   console.log(newObj)
}
convertObj(JSON.stringify(obj));
0赞 Sesha Ganesh 8/2/2022 #12

以下会将 objA 复制到 objB,而不引用 objA

let objA = { prop: 1 }, 
let objB = Object.assign( {}, objA )   
objB.prop = 2;

console.log( objA , objB )

评论

0赞 Community 8/4/2022
您的答案可以通过其他支持信息进行改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。
0赞 Soheil Saheb-jamii 1/5/2023 #13

您现在可以将 structuredClone() 用于深度对象克隆:

https://developer.mozilla.org/en-US/docs/Web/API/structuredClone

const newItem = structuredClone(oldItem);