javascript - 数组拷贝 - 深拷贝循环引用




在JavaScript中按值复制数组 (14)

将JavaScript中的数组复制到另一个数组时:

var arr1 = ['a','b','c'];
var arr2 = arr1;
arr2.push('d');  //Now, arr1 = ['a','b','c','d']

我意识到arr2是指与arr1相同的数组,而不是一个新的独立数组。 我如何复制数组来获得两个独立的数组?


slice()的替代方案是concat ,可以以两种方式使用。 其中第一个可能更具可读性,因为预期的行为非常明确:

var array2 = [].concat(array1);

第二种方法是:

var array2 = array1.concat();

科恩(在评论中)指出,后一种方法有更好的表现

这种方式的工作原理是, concat方法创建一个新的数组,其中包含调用该对象的对象中的元素,随后是传递给它的任何数组的元素作为参数。 所以当没有参数传递时,它只是复制数组。

Lee Penkman也在评论中指出,如果array1 undefined ,你可以返回一个空数组,如下所示:

var array2 = [].concat(array1 || []);

或者,对于第二种方法:

var array2 = (array1 || []).concat();

请注意,您也可以使用slice进行此操作: var array2 = (array1 || []).slice();


上面提到的一些方法在使用简单数据类型(如数字或字符串)时工作良好,但当数组包含其他对象时,这些方法会失败。 当我们试图从一个数组向另一个数组传递任何对象时,它将作为引用传递,而不是对象。

在您的JavaScript文件中添加以下代码:

Object.prototype.clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (i in this) {
        if (i == 'clone') 
            continue;
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        } 
        else 
            newObj[i] = this[i]
    } return newObj;
};

并简单地使用

var arr1 = ['val_1','val_2','val_3'];
var arr2 = arr1.clone()

它会工作。


使用jQuery深拷贝可以做如下:

var arr2 = $.extend(true, [], arr1);

在Javascript中,深度复制技术依赖于数组中的元素。
我们从这里开始。

三种元素

元素可以是:文字值,文字结构或原型。

// Literal values (type1)
var booleanLiteral = true;
var numberLiteral = 1;
var stringLiteral = 'true';

// Literal structures (type2)
var arrayLiteral = [];
var objectLiteral = {};

// Prototypes (type3)
var booleanPrototype = new Bool(true);
var numberPrototype = new Number(1);
var stringPrototype = new String('true');
var arrayPrototype = new Array();
var objectPrototype = new Object(); # or "new function () {}"

从这些元素我们可以创建三种类型的数组。

// 1) Array of literal-values (boolean, number, string) 
var type1 = [true, 1, "true"];

// 2) Array of literal-structures (array, object)
var type2 = [[], {}];

// 3) Array of prototype-objects (function)
var type3 = [function () {}, function () {}];

深度复制技术取决于三种阵列类型

根据数组中的元素类型,我们可以使用各种技术进行深度复制。

  • 字面值数组(type1)
    可以使用myArray.splice(0)myArray.slice()myArray.concat()技术来仅使用文字值(布尔值,数字和字符串)深度复制数组; 其中Slice比Concat具有更高的性能( http://jsperf.com/duplicate-array-slice-vs-concat/3 )。

  • 文字值(类型1)和文字结构(类型2)
    JSON.parse(JSON.stringify(myArray))技术可用于深度复制文字值(布尔值,数字,字符串)和文字结构(数组,对象),但不能使用原型对象。

  • 所有数组(type1,type2,type3)
    jQuery $.extend(myArray)技术可用于深度复制所有数组类型。 像UnderscoreLo-dash这样的库为jQuery $.extend()提供了类似的深层复制功能,但性能较低。 更令人惊讶的是, $.extend()具有比JSON.parse(JSON.stringify(myArray))技术更高的性能http://jsperf.com/js-deep-copy/15
    对于那些回避第三方库(比如jQuery)的开发者,你可以使用下面的自定义函数; 它比$ .extend具有更高的性能,并且可以深度复制所有阵列。

    function copy(o) {
      var output, v, key;
      output = Array.isArray(o) ? [] : {};
      for (key in o) {
        v = o[key];
        output[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
      }
      return output;
    }  
    

所以要回答这个问题......

var arr1 = ['a','b','c'];
var arr2 = arr1;

我意识到arr2是指与arr1相同的数组,而不是一个新的独立数组。 我如何复制数组来获得两个独立的数组?

回答

由于arr1是字面值(布尔值,数字或字符串)的数组,因此您可以使用上面讨论的任何深度复制技术,其中slice具有最高的性能。

// Highest performance for deep copying literal values
arr2 = arr1.slice();

// Any of these techniques will deep copy literal values as well,
//   but with lower performance.
arr2 = arr1.splice(0);
arr2 = arr1.concat();
arr2 = JSON.parse(JSON.stringify(arr1));
arr2 = $.extend(true, [], arr1); // jQuery.js needed
arr2 = _.extend(arr1); // Underscore.js needed
arr2 = _.cloneDeep(arr1); // Lo-dash.js needed
arr2 = copy(arr1); // Custom-function needed - as provided above

复制多维数组/对象:

function deepCopy(obj) {
   if (Object.prototype.toString.call(obj) === '[object Array]') {
      var out = [], i = 0, len = obj.length;
      for ( ; i < len; i++ ) {
         out[i] = arguments.callee(obj[i]);
      }
      return out;
   }
   if (typeof obj === 'object') {
      var out = {}, i;
      for ( i in obj ) {
         out[i] = arguments.callee(obj[i]);
      }
      return out;
   }
   return obj;
}

感谢James Padolsey提供此功能。

来源: Here



您也可以使用ES6传播运算符来复制Array

var arr=[2,3,4,5];
var copyArr=[...arr];

您可以使用数组传播...来复制数组。

const itemsCopy = [...items];

此外,如果想创建一个新的数组,现有的数组是其中的一部分:

var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];

现在所有主流浏览器支持数组传播,但如果您需要较早的支持,请使用打字稿或babel并编译为ES5。

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…


有新引入的Array.from ,但不幸的是,截至撰写本文时,它只支持最近的Firefox版本(32或更高版本)。 它可以简单地使用如下:

var arr1 = [1, 2, 3];
console.log(Array.from(arr1)); // Logs: [1, 2, 3]

参考: Here

或者Array.prototype.map可能与一个标识函数一起使用:

function identity(param)
{
    return param;
}

var arr1 = [1, 2, 3],
    clone = arr1.map(identity);

参考: Here


正如我们在Javascript中所知道的, 数组对象是通过引用的,但是我们可以通过什么方式复制数组而不更改原始数组呢?

这里有几个方法来做到这一点:

想象一下,我们在你的代码中有这个数组:

var arr = [1, 2, 3, 4, 5];

1)在函数中循环访问数组并返回一个新数组,如下所示:

 function newArr(arr) {
      var i=0, res = [];
      while(i<arr.length){
       res.push(arr[i]);
        i++;
       }
   return res;
 }

2)使用slice方法,slice用于对数组的一部分进行切片,如果不指定数组的开始和结束,它将切片数组的一部分而不接触原始数据,如果不指定数组的开始和结束,它将切片整个数组并基本上完成数组的完整副本,所以我们可以轻松地说:

var arr2 = arr.slice(); // make a copy of the original array

3)也联系方法,这是为了合并两个数组,但我们可以指定一个数组,然后基本上在新的联系数组中创建一个值的副本:

var arr2 = arr.concat();

4)也对字符串化和解析方法进行字符串分析,不建议这样做,但可以简单地复制数组和对象:

var arr2 = JSON.parse(JSON.stringify(arr));

5)Array.from方法,这并没有得到广泛的支持,在使用前请检查不同浏览器的支持情况:

var arr2 = Array.from(arr);

6)ECMA6的方式,也不完全支持,但babelJs可以帮助你,如果你想transpile:

let arr2 = [...arr];

添加到array.slice()的解决方案; 请注意,如果您有多维数组,子数组将被引用复制。 你可以做的是分别循环和切片()每个子数组

var arr = [[1,1,1],[2,2,2],[3,3,3]];
var arr2 = arr.slice();

arr2[0][1] = 55;
console.log(arr2[0][1]);
console.log(arr[0][1]);

function arrCpy(arrSrc, arrDis){
 for(elm in arrSrc){
  arrDis.push(arrSrc[elm].slice());
}
}

var arr3=[];
arrCpy(arr,arr3);

arr3[1][1] = 77;

console.log(arr3[1][1]);
console.log(arr[1][1]);

同样的事情发生在对象数组中,它们将通过引用被复制,您必须手动复制它们


用这个:

var newArray = oldArray.slice();

基本上, slice()操作克隆数组并将引用返回给新数组。 另请注意:

对于引用,字符串和数字(​​而不是实际对象),切片将对象引用复制到新数组中。 原始数组和新数组都指向同一个对象。 如果引用的对象发生更改,则这些更改对新数组和原始数组均可见。

字符串和数字等原语是不可变的,因此对字符串或数字的修改是不可能的。


这是我尝试了很多方法之后做到的:

var newArray = JSON.parse(JSON.stringify(orgArray));

这将创建一个与第一个不相关的新的深层副本(不是浅表副本)。

此外,这显然不会克隆事件和函数,但是您可以在一行中完成这件事,它可以用于任何类型的对象(数组,字符串,数字,对象...)


这里有几种复制方式:

const array = [1,2,3,4];

const arrayCopy1 = Object.values(array);
const arrayCopy2 = Object.assign([], array);
const arrayCopy3 = array.map(i => i);
const arrayCopy4 = Array.of(...array );





arrays