对于 JavaScript 多维数组的深层副本,深入一层似乎就足够了。这是真的吗?

Posted

技术标签:

【中文标题】对于 JavaScript 多维数组的深层副本,深入一层似乎就足够了。这是真的吗?【英文标题】:For a deep copy of a JavaScript multidimensional array, going one level deep seems sufficient. Is this reliably true? 【发布时间】:2014-09-25 20:47:29 【问题描述】:

注意:我只是一个新手编码,所以这个问题的核心可能存在明显的错误或误解。

基本上,我需要在 javascript 中将多维数组“按值”深度复制到未知深度。我认为这需要一些复杂的递归,但 似乎 在 JavaScript 中,您只需要复制一层深度即可按值复制整个数组。

作为一个例子,这是我的测试代码,使用了一个故意卷积的数组。

function test() 
  var arr = [ ['ok1'],[],[ [],[],[ [], [ [ ['ok2'], [] ] ] ] ] ];
  var cloned = cloneArray(arr);
  arr = '';   // Delete the original
  alert ( cloned );



function cloneArray(arr)   
  // Deep copy arrays. Going one level deep seems to be enough.
  var clone = [];
  for (i=0; i<arr.length; i++) 
    clone.push( arr[i].slice(0) )
  
  return clone;

在我运行此测试时(Ubuntu 上最新的稳定版 Chrome 和 Firefox),即使在删除原始数据之后,即使是数组的最深部分也似乎已成功复制克隆中的值,尽管事实上切片() “复制”只深入了一层。这是 JavaScript 中的标准行为吗?我可以依靠它来为旧版浏览器工作吗?

【问题讨论】:

在 *** 上,您不应编辑您的问题以提供最终答案。这就是最佳答案指定的用途。 *** 希望问题显示为您最初提出的问题(以及任何澄清的编辑),以便人们稍后返回时看到的上下文。 好的,我会将问题编辑回原来的状态。 【参考方案1】:

您的测试存在缺陷,是否正在制作真实副本,这使您得出的结论不正确,即您获得了嵌套数组中所有数据的完整副本。你只是在做一个两级副本,而不是一个 N 级副本。

Javascript 是一种垃圾收集语言,因此您实际上不会删除变量或对象,即使您尝试过,如果它在代码中的其他地方被引用,也不会影响同一个变量。要查看您是否真正拥有完全独立的副本,请尝试将对象嵌套两层深,然后更改原始数组中对象的属性。您会发现克隆数组中的相同对象发生了变化,因为您没有进行深度克隆。两个数组都引用了完全相同的对象。

这里是an example。

function cloneArray(arr)   
  // Deep copy arrays. Going one level deep seems to be enough.
  var clone = [];
  for (i=0; i<arr.length; i++) 
    clone.push( arr[i].slice(0) )
  
  return clone;


var x = [[foo: 1]];

var y = cloneArray(x);
x[0][0].foo = 2;

// now see what the value is in `y`
// if it's 2, then it's been changed and is not a true copy
// both arrays have a reference to the same object
console.log(y[0][0].foo);    // logs 2

如果第三层也是另一个数组,也会发生同样的结果。 您必须递归遍历对象类型的每个元素,然后克隆该对象本身以获得嵌套数组中所有内容的完整克隆。

如果您想要执行深度复制(到任意级别)并适用于所有数据类型的代码,请参阅here。

仅供参考,您的 cloneArray() 函数假定您的数组的所有第一级成员都是数组本身,因此如果它包含任何其他类型的值则不起作用。

【讨论】:

确实如你所说,我似乎遇到了垃圾收集行为。我将测试更改如下:link,对原始数组的更改确实影响了我所谓的“克隆”数组。碰巧的是,我可以相信数组的所有一阶(和二阶)成员本身都是数组,并且没有一个成员是对象。我想采用 DIY 方法,即使这意味着重新发明***(只是为了学习经验),所以我会尝试想出我自己的系统来可靠地克隆阵列。感谢您的帮助。【参考方案2】:

Array.prototype.slice不适合克隆数组

这应该很适合你

function deepClone(arr) 
  var len = arr.length;
  var newArr = new Array(len);
  for (var i=0; i<len; i++) 
    if (Array.isArray(arr[i])) 
      newArr[i] = deepClone(arr[i]);
    
    else 
      newArr[i] = arr[i];
    
  
  return newArr;

如果您需要支持旧版浏览器,请务必使用此polyfill(通过 MDN)

if(!Array.isArray) 
  Array.isArray = function(arg) 
    return Object.prototype.toString.call(arg) === '[object Array]';
  ;

【讨论】:

看起来你只是在做数组的深拷贝,而不是对象。 嗯,是的,这符合问题的要求。提问者没有提到复制(非数组)对象的要求。【参考方案3】:

您的代码不起作用

如果您的数组包含其他变量,例如数字或字符串(不仅是数组),它将失败,因为它将调用arr[i].slice(),但由于arr[i] 是一个数字,它没有任何@987654324 @property,这将引发错误。 在任何情况下,您的函数都会保留对数组中对象和其他内容的所有引用。因此,您实际上不会获得数组的副本。

例子:

var a = [1,2, [11,13], ['ok']];
var b = cloneArray(a);

> TypeError: undefined is not a function // because numbers have no .slice method

解决办法:

要复制数组,您需要对其进行深层复制。由于创建深拷贝需要一个使用递归的函数来深拷贝主对象或数组中的任何对象或数组,因此最简单的方法是使用 jQuery 及其 .extend 方法,该方法执行数组,请参阅here 了解更多信息。

var a =[[1], [2], [3]];
var b = $.extend(true, [], a);

b[0][0] = 99;
a[0][0] // still 1

【讨论】:

这绝不是问题的答案。 除了 jQuery 根本不是问题的一部分。如果 OP 提到 jQuery 或标记 jQuery,则解决方案通常只需要 jQuery。就目前而言,这是一个简单的 javascript 问题。另外,为什么你的第一个要点提到.split(),而这与问题或答案无关?【参考方案4】:

这是我“克隆”多维数组的递归方法。它一直运行到最深层次:

if ( !Array.clone )

        Array.prototype.clone = function()
        
                var _arr = ( arguments[0] == null ) ? [] : arguments[0] ; 
                for( var _p = 0 ; _p < this.length ; _p++ )
                
                         if ( this[_p] instanceof Array )
                         
                                 var _sub = [] ;
                                 this[_p].clone( _sub ) ;
                                 _arr.push( _sub.slice() );
                         
                         else _arr.push( this[_p] );
                

                return _arr ;
        

现在试试这个代码:

var _a = [ "a", "b", [ "c", "d", [ "e", "f" ] ] ];
var _b = _a.clone();
console.log( _b );

var _a 和 _b 是两个不同的对象:如果您从 var _b 中删除一个元素,那么 var _a 不会受到影响。

【讨论】:

以上是关于对于 JavaScript 多维数组的深层副本,深入一层似乎就足够了。这是真的吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ruby 中创建对象的深层副本?

JavaScript数组常用方法解析和深层次js数组扁平化

Typescript 用数组传播深拷贝

对象数组的深拷贝

PHP 多维数组

多维布尔数组检查Javascript中是不是全部为真