对于JavaScript多维数组的深层副本,深入一级似乎就足够了。

For a deep copy of a JavaScript multidimensional array, going one level deep seems sufficient. Is this reliably true?

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

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

作为一个例子,这里是我的测试代码,使用一个故意复杂的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)时,即使是最深的数组部分,似乎也能通过克隆中的值成功复制,即使在原始部分被删除之后,尽管slice()"copying"只进行了一层深度。这是JavaScript中的标准行为吗?我可以依靠它来为旧的浏览器工作吗?


array.prototype.slice不适用于克隆数组

这对你很有用

1
2
3
4
5
6
7
8
9
10
11
12
13
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)

1
2
3
4
5
if(!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}


您的测试存在缺陷,即是否正在进行真正的复制,这使得您的结论不正确,即您正在获取嵌套数组中所有数据的完整副本。您只进行两级复制,而不是N级复制。

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

这是一个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

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

如果您希望代码能够执行深度复制(到任意级别),并适用于所有数据类型,请参见此处。

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


您的代码不起作用:

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

例子:

1
2
3
4
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方法,请参见此处以获取更多信息。

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

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


这是我对多维数组"克隆"的递归方法。它一直延伸到最深处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 ;
        }
}

现在尝试此代码:

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

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