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

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

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


不需要jQuery。。。工作示例

var arr2 = arr1.slice()

这将从起始位置0到阵列末端复制阵列。

重要的是要注意,它将按照原始类型(字符串、数字等)的预期工作,并解释引用类型的预期行为。。。

如果您有一个引用类型数组,那么就说Object类型。将复制该数组,但这两个数组都将包含对同一对象的引用。因此,在这种情况下,即使实际复制了数组,也似乎通过引用复制了数组。


使用此项:

让oldArray=[1,2,3,4,5];let newArray=oldArray.slice();console.log({newArray});

基本上,slice()操作克隆数组并返回对新数组的引用。

还应注意:

对于引用、字符串和数字(而不是实际对象),slice()将对象引用复制到新数组中。原始数组和新数组都引用同一对象。如果引用的对象发生更改,则这些更改对新阵列和原始阵列都可见。

字符串和数字等基元是不可变的,因此不可能更改字符串或数字。


如果要创建对象或数组的新副本,必须显式复制对象或数组元素的财产,例如:

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

for (var i=0; i < arr1.length; i++) {
   arr2[i] = arr1[i];
}

您可以在Google上搜索有关不可变原始值和可变对象引用的更多信息。


切片的另一种选择是凹面,可以用两种方式。第一个可能更容易理解,因为预期行为非常清楚:

var array2 = [].concat(array1);

第二种方法是:

var array2 = array1.concat();

科恩(在评论中)指出,后一种方法具有更好的性能。

其工作方式是concat方法创建一个新数组,该数组由调用它的对象中的元素组成,后跟作为参数传递给它的任何数组的元素。因此,当没有传递参数时,它只是复制数组。

Lee Penkman也在评论中指出,如果array1有可能未定义,可以返回一个空数组,如下所示:

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()

这会奏效的。


这里有一个变体:

var arr1=['a', 'b', 'c'];
var arr2=eval(arr1.toSource());
arr2.push('d');
console.log('arr1: '+arr1+'\narr2: '+arr2);
/*
 *  arr1: a,b,c
 *  arr2: a,b,c,d
 */

添加到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 = JSON.parse(JSON.stringify(orgArray));

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

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


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

三种类型的元素

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

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

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

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

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

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

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

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

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

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

深度复制技术

基准

https://www.measurethat.net/Benchmarks/Show/17502/0/deep-copy-comparison

文字值数组(类型1)[…myArray]、myArray.splice(0)、myArrax.slice()和myArray.concat()技术可用于深度复制仅具有文字值(布尔值、数字和字符串)的数组;其中slice()在Chrome中具有最高的性能,并且扩展。。。在Firefox中具有最高的性能。文本值(类型1)和文本结构(类型2)的数组JSON.parse(JSON.stringify(myArray))技术可用于深度复制文本值(布尔值、数字、字符串)和文本结构(数组、对象),但不能复制原型对象。所有数组(类型1、类型2、类型3)Lo-dash-cloneDeep(myArray)或jQuery-extend(true,[],myArray)技术可用于深度复制所有数组类型。其中Lodash cloneDeep()技术具有最高的性能。对于那些避免使用第三方库的用户,下面的自定义函数将深度复制所有数组类型,性能低于cloneDeep(),性能高于extend(true)。

function copy(aObject) {
  // Prevent undefined objects
  // if (!aObject) return aObject;

  let bObject = Array.isArray(aObject) ? [] : {};

  let value;
  for (const key in aObject) {

    // Prevent self-references to parent object
    // if (Object.is(aObject[key], aObject)) continue;
    
    value = aObject[key];

    bObject[key] = (typeof value === "object") ? copy(value) : value;
  }

  return bObject;
}

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

问题

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

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

答复

因为arr1是一个文本值数组(布尔值、数字或字符串),所以可以使用上面讨论的任何深度复制技术,其中slice()和spread。。。具有最高的性能。

arr2 = arr1.slice();
arr2 = [...arr1];
arr2 = arr1.splice(0);
arr2 = arr1.concat();
arr2 = JSON.parse(JSON.stringify(arr1));
arr2 = copy(arr1); // Custom function needed, and provided above
arr2 = _.cloneDeep(arr1); // Lo-dash.js needed
arr2 = jQuery.extend(true, [], arr1); // jQuery.js needed

在我的特殊情况下,我需要确保阵列保持完整,所以这对我来说很有用:

// Empty array
arr1.length = 0;
// Add items from source array to target array
for (var i = 0; i < arr2.length; i++) {
    arr1.push(arr2[i]);
}

制作多维数组/对象的副本:

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;
}

感谢詹姆斯·帕多尔西担任这一职务。

来源:此处


这是最新推出的Array.from,但不幸的是,截至本文撰写之时,它仅在最新的Firefox版本(32及更高版本)上受支持。它可以简单地使用如下:

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

参考:此处

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

function identity(param)
{
    return param;
}

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

参考:此处


如果您在ECMAScript 6环境中,使用Spread Operator,您可以这样做:

var arr1=[‘a’,‘b’,‘c’];var arr2=[…arr1]//复制arr1arr2.推('d');控制台日志(arr1)控制台日志(arr2)<script src=“http://www.wzvang.com/snippet/ignore_this_file.js“></script>


您可以使用阵列排列。。。复制阵列。

const itemsCopy=[…items];

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

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

现在所有主流浏览器都支持数组排列,但如果您需要更旧的支持,请使用typescript或babel并编译到ES5。

有关点差的更多信息


从ES2015开始,

var arr2 = [...arr1];

我个人认为Array.from是一个更具可读性的解决方案。顺便说一句,请注意它的浏览器支持。

//克隆设x=[1,2,3];设y=数组.来自(x);console.log({y});//深层克隆让clone=arr=>Array.from(arr,item=>Array.isArray(item)?克隆(项目):项目);x=[1,[],[[]]];y=克隆(x);console.log({y});


使用jQuery深度复制可以如下所示:

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

正如我们在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) 还有contact方法,这是用于合并两个数组,但我们可以只指定其中一个数组,然后这基本上会复制新接触数组中的值:

var arr2 = arr.concat();

4) 另外,不建议使用stringify和parse方法,但这是复制Array和Objects的一种简单方法:

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

5) Array.from方法,这不受广泛支持,使用前请检查不同浏览器中的支持:

const arr2 = Array.from(arr);

6) ECMA6方式,也不完全支持,但babelJs可以帮助您,如果您想转换:

const arr2 = [...arr];

还可以使用ES6排列运算符复制阵列

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

以下是其他几种复制方法:

常量数组=[1,2,3,4];const arrayCopy1=对象.值(数组);const arrayCopy2=对象赋值([],数组);const arrayCopy 3=array.map(i=>i);const arrayCopy4=数组.of(…Array);


对于包含对象的ES6阵列

cloneArray(arr) {
    return arr.map(x => ({ ...x }));
}

丹,没必要耍花样。你所需要做的就是通过这样做复制arr1。

var arr1=[‘a’,‘b’,‘c’];var arr2=[];var arr2=新数组(arr1);arr2.推送('d');//现在,arr2=[['a','b','c'],d']console.log('arr1:');控制台日志(arr1);console.log('arr2:');控制台日志(arr2);//以下是诀窍:var arr3=[…arr1];arr3.推送('d');//现在,arr3=[‘a’,‘b’,‘c’,‘d’];console.log('arr3:');控制台日志(arr3);

现在arr1和arr2是存储在单独堆栈中的两个不同数组变量。在jsfiddle上看看这个。


您可以通过以下方式实现:arr2=arr1.map(x=>Object.assign({},x));


您可以将ES6与spread Opartor一起使用,它更简单。

arr2 = [...arr1];

有一些限制。。check docs Spread语法@mozilla


当我们想要使用赋值运算符(=)复制数组时,它不会创建副本,只会复制指向数组的指针/引用。例如:

常量oldArr=[1,2,3];const newArr=旧Arr;//现在oldArr指向内存中的相同位置console.log(oldArr==newArr);//指向内存中的相同位置,因此是正确的常量拷贝=[1,2,3];console.log(copy==newArr);//不指向内存中的相同位置,因此是错误的

通常,当我们转换数据时,我们希望保持初始数据结构(例如数组)的完整性。我们通过制作数组的精确副本来实现这一点,这样可以在初始数组保持不变的情况下转换该数组。

复制阵列的方法:

常量oldArr=[1,2,3];//使用扩展运算符将旧值扩展到新数组文本中const newArr1=[…oldArr];//不带参数的切片返回新复制的数组const newArr2=oldArr.slice();//Map将回调应用于数组中的每个元素并返回一个新数组const newArr3=旧Arr.map((el)=>el);//Concat用于合并数组并返回一个新数组。不带参数的凹形复制数组const newArr4=oldArr.contat();//Object.assign可用于将所有财产转换为新的数组文字const newArr5=对象赋值([],oldArr);//使用new关键字通过Array构造函数创建const newArr6=新数组(…oldArr);//For循环函数克隆(基本){常量newArray=[];for(设i=0;i<base.length;i++){newArray[i]=基[i];}return newArray;}const newArr7=克隆(oldArr);console.log(newArr1、newArr2、newArr3、newArr4、newArr5、newArr6、newArr7);

嵌套数组或对象时要小心!:

嵌套数组时,将通过引用复制值。以下是一个示例,说明这可能会导致问题:

设arr1=[1,2,[1,2]]设arr2=[…arr1];arr2[2][0]=5;//我们改变arr2console.log(arr1);//arr1也被更改,因为arr1内部的数组是通过引用复制的

因此,当数组中存在要复制的对象或数组时,不要使用这些方法。即,仅在基元数组上使用这些方法。

如果您确实想深度克隆javascript数组,请将JSON.parse与JSON.stringify结合使用,如下所示:

设arr1=[1,2,[1,2]]让arr2=JSON.parse(JSON.stringify(arr1));arr2[2][0]=5;console.log(arr1);//现在我没有被修改,因为我是一个深度克隆

复制性能:

因此,我们选择哪一种来获得最佳性能。结果证明,最冗长的方法,for循环具有最高的性能。使用for循环进行真正的CPU密集型复制(大/多阵列)。

在此之后,.slice()方法也有不错的性能,而且不那么冗长,程序员更容易实现。我建议将.slice()用于日常复制不太占用CPU的数组。如果不需要深度克隆,并且性能存在问题,还应避免使用JSON.parse(JSON.stringify(arr))(大量开销)。

源性能测试


重要的

这里的大多数答案适用于特定情况。

如果您不关心深度/嵌套对象和道具,请使用(ES6):

let clonedArray=[…array]

但如果要进行深度克隆,请改用以下方法:

let cloneArray=JSON.parse(JSON.stringify(数组))*

*函数在使用stringify时不会被保存(序列化),如果没有它们,您将得到结果。


对于lodash用户:

let clonedArray=_.clone(数组)文档

and

let cloneArray=_.cloneDeep(数组)文档


快速示例:

如果数组中的元素是基元类型(字符串、数字等)

var arr1=[‘a’,‘b’,‘c’];//arr1和arr2是独立的,基本元素存储在//记忆中的不同位置var arr2=arr1.slice();arr2.推('d');console.log(arr1);//[“a”、“b”、“c”]console.log(arr2);//[“a”、“b”、“c”、“d”]

如果数组中的元素是对象文本,则另一个数组({},[])

var arr1=[{x:'a',y:'b'},[1,2],[3,4]];//arr1和arr2是独立的,引用的/地址存储在不同的//记忆中的位置。但这些参考文献/地址指向了一些共同的地方//在存储器中。var arr2=arr1.slice();arr2.pop();//好-不影响arr1 bcos,只有arr2中的地址是//删除的不是该地址所指向的数据arr2[0].x=“z”;//不正常-影响在公共区域进行的arr1 bcos更改//由arr1和arr2中的地址指示arr2[1][0]=9;//不正常-与上述原因相同console.log(arr1);//[{x:'z',y:'b'},[9,2],[3,4]]console.log(arr2);//[{x:'z',y:'b'},[9,2]]

解决方案2:逐元素深度复制

var arr1=[{x:'a',y:'b'},[1,2],[3,4]];arr2=JSON.parse(JSON.stringify(arr1));arr2.pop();//OK-不影响arr1arr2[0].x=“z”;//好-不影响arr1arr2[1][0]=9;//OK-不影响arr1console.log(arr1);//[{x:“a”,y:“b”},[1,2],[3,4]]console.log(arr2);//[{x:'z',y:'b'},[9,2]]


如果您的数组包含原始数据类型的元素,如int、char或string等,那么您可以使用其中一种方法来返回原始数组的副本,如.slice()或.map()或spread运算符(得益于ES6)。

new_array = old_array.slice()

or

new_array = old_array.map((elem) => elem)

or

const new_array = new Array(...old_array);

但是如果您的数组包含复杂元素,例如对象(或数组)或更多嵌套对象,那么,您必须确保从顶层到最后一层复制所有元素,否则将使用内部对象的引用,这意味着更改new_array中object_elements的值仍将影响旧_array。您可以将这种复制方法称为“深度复制”旧数组的。

对于深度复制,您可以根据数据类型在每个级别对原始数据类型使用上述方法,也可以使用这种成本高昂的方法(如下所述)进行深度复制,而无需做大量工作。

var new_array = JSON.parse(JSON.stringify(old_array));

根据您的需求,还有很多其他方法可以使用。我只提到了其中的一些,它们可以让我们大致了解当我们试图按值将一个数组复制到另一个数组时会发生什么。


let a = [1,2,3];

现在,您可以执行以下任一操作来创建阵列的副本。

let b = Array.from(a); 

OR

let b = [...a];

OR

let b = new Array(...a); 

OR

let b = a.slice(); 

OR

let b = a.map(e => e);

现在,如果我改变一个,

a.push(5); 

那么,a是[1,2,3,5],但b仍然是[1,3,3],因为它有不同的引用。

但我认为,在上述所有方法中,Array.from都更好,并且主要用于复制数组。


以下是如何对可变深度的基元数组执行此操作:

// If a is array: 
//    then call cpArr(a) for each e;
//    else return a

const cpArr = a => Array.isArray(a) && a.map(e => cpArr(e)) || a;

let src = [[1,2,3], [4, ["five", "six", 7], true], 8, 9, false];
let dst = cpArr(src);

https://jsbin.com/xemazog/edit?js安慰


基本体值始终按其值传递(复制)。但是,复合值是通过引用传递的。

那么我们如何复制这个arr?

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

在ES6中复制阵列

let arrCopy = [...arr]; 

在ES5中复制n阵列

let arrCopy = arr.slice(); 
let arrCopy = [].concat(arr);

为什么“let arrCopy=arr”不按值传递?

将一个变量传递给另一个复合值(例如“对象/数组”)会有不同的行为。在共和值上使用sign运算符,我们将引用传递给对象。这就是为什么在删除/添加arr元素时,两个数组的值都会发生变化。

例外情况:

arrCopy[1] = 'adding new value this way will unreference';

为变量指定新值时,将更改引用本身,而不会影响原始对象/数组。

阅读更多信息


只是在写:

arr2 = arr1.concat();

您正在使用第一个数组的副本生成新数组。请注意,这是将元素推入数组的一种功能方式。

如果您的代码基于ES6,您也可以使用扩展运算符:

arr2 = [...arr1];

我个人更喜欢这种方式:

JSON.parse(JSON.stringify( originalObject ));

当我在处理一系列深度嵌套的对象时,这些选项对我都不起作用。对于ES6,我发现这个解决方案很有用。

const old_array = [{name:"Nick", stats:{age:25,height:2}},{name:"June", stats:{age:20,height:2}}];

const new_array = old_array.map(e => {
  if (e.name === 'June') {
     e = { ...e };
     e.stats = {...e.stats, age: 22};
   }
  return e;
});

只有new_array将受此影响。


当有很多答案时,您必须使用最佳实践来解决这个问题。

我建议您使用阵列排列…复制阵列。

var arr1=[‘a’,‘b’,‘c’];

var arr2=[…arr1];


structuredClone是一种新的深度克隆方法。

structuredClone(value)
structuredClone(value, { transfer })

transfer是一个值可转移对象数组,它将被移动而不是克隆到返回的对象中。

你可能会发现它的算法很有趣。


var arr2=arr1.slice(0);

这种方法只适用于简单数组。

如果您有类似复杂阵列的对象阵列,则必须使用其他解决方案,如:

const arr2 = JSON.parse(JSON.stringify(arr1)); 

例如,我们有一个对象数组,每个单元格的对象中都有另一个数组字段。。。在这种情况下,如果我们使用切片方法,那么数组字段将通过Ref进行复制,这意味着这些字段更新将影响原始数组中的相同元素和字段。


在研究之后,我发现一个干净的方法可以是:

  const arr1 = [['item 1-1', 'item 1-2'], ['item 2-1', 'item 2-2'], ['item 3-1', 'item 3-2']];

  /**
   * Using Spread operator, it will create a new array with no reference to the first level.
   * 
   * Since, the items are not primitive, they get their own references. It means that any change on them,
   * it will be still reflected on the original object (aka arr1).
   */
  const arr2 = [...arr1];

  /**
   * Using Array.prototype.map() in conjunction Array.prototype.slice() will ensure:
   * - The first level is not a reference to the original array.
   * - In the second level, the items are forced (via slice()) to be created as new ones, so there is not reference to the original items
   */
  const arr3 = arr1.map(item => item.slice());

您需要了解要使用的阵列的复杂性,然后应用最佳解决方案(即➡️ 引用数组中的引用项)


我发现这种方法比较容易:

设arr=[1,2,3,4,5];让newArr=[…arr];console.log(newArr);