创建多维数组的副本而非引用 - JavaScript

105

这也被称为“深度复制”,我找到了一些文章。最接近的似乎是这个,但它是针对jQuery的 - 我想在不使用库的情况下完成此操作。

我还看到,在两个地方,有可能做到类似以下的事情:

arr2 = JSON.decode(JSON.encode(arr1));

但这显然是低效的。也可以循环和逐个复制每个值,并遍历所有数组。那看起来同样很繁琐和低效。

那么,复制一个JavaScript多维数组[[a],[b],[c]]最有效的非库方法是什么?如果必要,我完全可以接受“非IE”方法。

谢谢!


1
还有任何循环引用需要处理吗? - I Hate Lazy
1
我会确保将备选方案与 JSON 解码 + 编码进行基准测试。制作字符串只是为了解码它们似乎很糟糕,但它在本机优化代码中完成,最终可能会使其更快。 - goat
1
@RandyHall:啊,我想我明白了。如果你只是在任何给定时间内真正处理它们的一级深度,并且如果它们是实际的数组,那么我只需迭代当前数组并使用其嵌套数组上的.slice()构建一个新数组。这将非常快速。 - I Hate Lazy
我会给你们一点小惊喜,因为你们都是这么好的运动员。目前看起来只能在Chrome浏览器中使用(就我测试过的范围而言),但是嘿,它还是很酷的。它目前正在使用循环来分配所述数组的各个值。请查看http://zerofaction.com。 - Randy Hall
1
如果您不将对象作为元素处理(或者不感兴趣复制它们),则可以使用以下代码:matrix.map((row) => [...row]); - Nicolás Fantone
显示剩余12条评论
5个回答

126

因为你似乎处理的是一个未知深度的数组,但你只需要在任何给定时间处理一层深度,所以使用.slice()将会简单和快速。

var newArray = [];

for (var i = 0; i < currentArray.length; i++)
    newArray[i] = currentArray[i].slice();

或者使用.map()代替for循环:

var newArray = currentArray.map(function(arr) {
    return arr.slice();
});

这将迭代当前数组,并构建一个新的由嵌套数组的浅拷贝组成的数组。然后当您进入下一层深度时,您会做同样的事情。

当然,如果存在混合数组和其他数据,则需要在切片之前测试其类型。


我猜地图的额外函数调用会使大多数浏览器变得更慢,尽管可以通过内联进行优化,然后可能会表现得更好。 - Bergi
2
哇,这个地图真的很干净。 - Dan Tang
目前我们只需要调用.slice(0)来克隆一个数组。使用.slice()也可以,但是.slice(0)更快。 - Trung Le Nguyen Nhat
7
现今,你可以使用 ES6 简写来让这段代码更加简洁。var newArray = currentArray.map(arr => arr.slice()); - Matt Pengelly
3
这不是“深度”克隆!只有第二级深度按值进行复制。所有较深层次的内容仍按引用进行复制,如果原始对象/数组更改,这些内容仍将发生变化。如果您想要完全克隆的对象,使用JSON解析会更加安全。 - techexpert
显示剩余2条评论

27

我不确定JSON.stringifyJSON.parse相比encodedecode有多大的优势,但您可以尝试:

JSON.parse(JSON.stringify(array));

我发现了另外一个东西(虽然我会稍作修改):

http://www.xenoveritas.org/blog/xeno/正确克隆JavaScript数组的方法

function deepCopy(obj) {
  if (typeof obj == 'object') {
    if (isArray(obj)) {
      var l = obj.length;
      var r = new Array(l);
      for (var i = 0; i < l; i++) {
        r[i] = deepCopy(obj[i]);
      }
      return r;
    } else {
      var r = {};
      r.prototype = obj.prototype;
      for (var k in obj) {
        r[k] = deepCopy(obj[k]);
      }
      return r;
    }
  }
  return obj;
}

1
var x = {}; x.y = x; 每次调用 deepCopy 都会触发 else 语句,并且 JSON 不能处理自引用,因为它本质上是基于树形结构的。 - Jeff
我并不是说你的答案不好(如果你知道你没有自我引用,那就很好),但它并不完全通用。 - Jeff
1
r.prototype = obj.prototype; 这段代码的作用是什么?在我看来似乎非常错误。 - Bergi
1
@RandyHall,Jeff:不,它并没有。它只是创建了一个名为prototype的属性。这并没有像__proto__那样设置继承。正确的方法是:Object.create(Object.getPrototypeOf(obj));,就像@Danny的答案中所示。 - Bergi
1
@Bergi,我不会撒谎,我复制并粘贴了我提供的链接,没有思考。我开始查看代码,意识到我需要更改多个部分(但我忽略了prototype 部分)。我希望能更好地理解原型和构造函数,以便更深入地了解,但感谢您正确地复制了原型的方式。 - Ian
显示剩余5条评论

6

由于您要求性能,我猜您可能会选择非通用解决方案。要复制已知层数的多维数组,应该使用最简单的解决方案,一些嵌套的for循环。对于您的二维数组,代码如下:

var len = arr.length,
    copy = new Array(len); // boost in Safari
for (var i=0; i<len; ++i)
    copy[i] = arr[i].slice(0);

要扩展到更高维数组,可以使用递归或嵌套的for循环!本地的slice方法比自定义的for循环更高效,但它不会创建深层副本,因此我们只能在最低级别使用它。

3
任何不会重复访问同一节点的递归算法,在javascript中都可以达到最高效的水平(至少在浏览器中),在其他语言的某些情况下,你可能可以通过复制内存块来获得更好的效果,但是javascript显然没有这个能力。我建议找到已经实现过该算法的人,并使用他们的实现来确保你正确无误地进行操作 - 它只需要定义一次。

1
您可以使用现代方式 structuredClone 在JavaScript中创建一个多维数组的副本。

var arr1 = [[1,2],[3,4],[5,6]];
var arr2 = structuredClone(arr1); //copy arr1
arr2.push([7,8]);

console.log(arr1); //[[1,2],[3,4],[5,6]]
console.log(arr2); //[[1,2],[3,4],[5,6],[7,8]]


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