在JavaScript中将一个对象数组复制到另一个数组而不保留对象引用(深拷贝)

72

我有一个情景,需要将对象数组(主数组)复制到另一个临时数组中,这个临时数组不应该与对象的引用相关联。如果我对主数组进行任何修改,不应该反映在临时数组中,这样我就可以独立地保存副本。

我使用了stackoverflow上的代码片段之一,它部分地实现了此目标,例如如果我从主数组中删除所有对象,则临时数组仍将保留值。但是当我对主数组进行某些修改并单击取消按钮时,我使用array.Removeall()从主数组中删除所有对象,但修改仍然存在于临时数组中,这意味着对象具有引用。

clone: function (existingArray) {
  var newObj = (existingArray instanceof Array) ? [] : {};
  console.debug('newObj value is ' + newObj);
  for (i in existingArray) {
    console.debug('i value is' + i);
    if (i == 'clone') continue;
    console.debug('existingArray[i] value ' + existingArray[i]);
    if (existingArray[i] && typeof existingArray[i] == "object") {

      newObj[i] = this.clone(existingArray[i]);
    } else {
      console.debug('in else part ' + existingArray[i]);
      newObj[i] = existingArray[i];
    }
  }
  return newObj;
}

我的对象结构如下所示

我正在使用 Knockout 框架。

newObjectCreation = function (localIp, RemoteIp, areaId) {
  this.localIP = ko.observable(localIp);
  this.remoteIP = ko.observable(RemoteIp);
  this.areaId = ko.observable(areaId);
};

template.ProtocolArray.push(new newObjectCreation('', '', '')); // to create default row

1
对象中是否包含任何无法表示为 JSON(非对象字面量)的内容?如果没有,您可以简单地执行以下操作:var clone = JSON.parse(JSON.stringify(src)); - Yoshi
相关:https://dev59.com/83VD5IYBdhLWcg3wAGiD - AlikElzin-kilaka
可能是JavaScript中复制数组的值的重复问题。 - kchetan
9个回答

137

让我理解一下:你不仅想要一个新的数组,而且想要为数组中存在的所有对象创建一个新实例?因此,如果你修改了临时数组中的一个对象,那么这种更改不会传播到主数组中?

如果是这种情况,那就取决于你在主数组中保存的值。如果这些对象是简单对象,并且它们可以序列化为JSON,则最快的方法是:

var tempArray = JSON.parse(JSON.stringify(mainArray));
如果你有更复杂的对象(例如由自己创建的某些构造函数、HTML节点等实例),那么你需要一个特定的方法来处理。
编辑:
如果你的newObjectCreation没有任何方法,你可以使用JSON,但构造函数不会相同。否则,你必须手动复制。
var tempArray = [];
for (var i = 0, item; item = mainArray[i++];) {
    tempArray[i] = new newObjectCreation(item.localIP, item.remoteIP, item.areaId);
}

  1. 那么,var tempArray = JSON.parse(JSON.stringify(mainArray));会进行深度复制,即不带对象引用吗?
  2. 关于手动复制,这种手动复制是否仍然指向相同的对象引用。
- sriramdev
如果你只有属性而没有方法,并且你不关心是否拥有完全相同构造函数的对象,而只是拥有具有相同值的普通对象,那么你将拥有一种类似于“深复制”的效果。为了使它更清晰:function foo(a) { this.a = a }; var main = [new foo(1)], temp = JSON.parse(JSON.stringify(main)); 那么你将得到 main[0].a 等于 1 以及 temp[0].a。如果你改变了后者,即 temp[0].a = 4,这种变化不会反映到 main[0].a 上。然而,main[0].constructorfoo,而 temp[0].constructorObject - ZER0
非常感谢您的回答,我很感激您提供的帮助模板。我已经使用了上面的代码片段,并且在knockout框架中按预期工作。向您致敬 :) - sriramdev
1
为什么JavaScript没有一个可以做到这一点的函数?像 function cloneObject (o) { return JSON.parse(JSON.stringify(o)); } ...除了在幕后执行更快的操作。或者更好的是:所有对象都有一个名为clone()的方法,这样你就可以只需执行mainArray.clone() - Luke
1
谢谢@ZER0!使用本地JSON函数是我见过的最优雅的建议。 - ryanttb

36

对于一些有同样问题的人,你也可以这样做。
使用新的es6特性,你可以创建一个数组的副本(不带引用),以及每个对象的副本,但是只能去掉一层引用。

const copy = array.map(object => ({ ...object }))

在我看来,它更加功能强大和惯用。

注意:扩展语法只会深入一层进行数组复制。因此,它可能不适合复制多维数组,如下面的示例所示(Object.assign()和spread语法也是如此)。
更多信息:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

因此,如果您的对象没有对象作为属性,则这种语法就是您所需的一切。不幸的是,规范中没有“开箱即用”的深度克隆功能,但如果需要,您可以随时使用库。

浏览器兼容性警告:我认为现在它已经成为Ecma的规范的一部分,但某些浏览器尚未完全支持扩展语法。但使用其中一个流行的转换器,您将不会有问题。


2
节省了许多工作时间 - showtime
你知道这种方法是否比使用 JSON.parse(JSON.stringify(mainArray)); 更快吗? - Vitomir
1
@Vitomir,这个解决方案现在更加有效。parse + stringify已经是“老派”的了。 - Camille

22

Lodash可以用于深度复制对象_.cloneDeep(value)


var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// → false

19

要复制一个数组的值而不是数组的引用,你可以简单地执行以下操作:

const tempArray = [...mainArray];

这是AirBnb JS Style Guide推荐的解决方案:https://github.com/airbnb/javascript#arrays

然而,这不会为数组内部的对象创建新的引用。要为数组及其内部的对象创建新引用,可以执行以下操作:

JSON.parse(JSON.stringify(mainArray));

20
数组本身没有引用,但是数组中的对象有。 - eomeroff
2
对于一个数组的数组,可以尝试这样做:var start = [[4,2,6,5],[1,8,7,3]]; var temp = start.map(e => e.slice(0)); var tempEs6 = start.map(e => [...e]); - Katie

10

你想要一个没有对象引用的深拷贝吗?没问题,请使用.slice()

例如:

var mainArr = [],
    tmpArr = []

tmpArr = mainArr.slice(0) // Shallow copy, no reference used.

顺便提一下:我认为双层JSON解析在性能方面可能不是最佳选择,这里有性能测试。


21
在Google Chrome 25上,这对我没有起作用。在我修改新数组的第一个对象后,它也修改了原始数组的第一个对象。不过我不会点踩,因为它可以处理简单数组,只是不能处理对象数组,这可能是帖子作者的观点。 - Purefan
这对我也没用。https://dev59.com/yV4b5IYBdhLWcg3w9Fyr - jsbisht
1
但是双重JSON解析有效...根据MDN (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 的说明: 对于对象引用(而不是实际对象),slice将对象引用复制到新数组中。原始数组和新数组都指向同一个对象。如果引用的对象发生更改,则更改对新数组和原始数组都可见。 - El Kopyto
3
如果数组中包含对象,这种方法就不起作用了。它会复制对象引用。 - Scotty
1
正如大家所说,除非数组中有简单对象,否则这根本行不通。在大多数情况下,使用大多数JS框架时都无法正常工作(这是一种过度简化的说法)。这个答案并没有回答问题。 - dewwwald
显示剩余2条评论

3

1
在嵌套数组中,您可以执行以下操作:
    const origin = [{ cat: 'Bengal', dog: 'Birman' }, { cat: 'Abyssinian', dog: 'Bombay' }];
    const clone = [];
    origin.forEach(v=> clone.push(Object.assign({}, v)));

1

0
使用 angular.copy。但不要对整个数组使用(因为它会通过引用传递数组项),而是迭代它并在其成员上使用 angular.copy
var newArray = [];
for (var i = 0, item; item = mainArray[i];) {
    newArray[i] = angular.copy(item);
    i++;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接