按值复制数组

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

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

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

1178178 次浏览

不需要jQuery…工作示例

var arr2 = arr1.slice()

这将复制从起始位置0到数组末尾的数组。

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

如果你有一个引用类型的数组,比如类型Object。数组被复制,但两个数组都将包含对相同Object的引用。所以在这种情况下,即使数组实际上是复制的。看起来像是通过引用复制数组

使用这个:

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

基本上,#0操作克隆数组并返回对新数组的引用。

还注意到:

对于引用、字符串和数字(而不是实际对象),#0将对象引用复制到新数组中。原始数组和新数组都引用同一个对象。如果引用的对象发生变化,则新数组和原始数组都可以看到这些变化。

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

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

var arr1 = ['a','b','c'];var arr2 = [];
for (var i=0; i < arr1.length; i++) {arr2[i] = arr1[i];}

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

#0的替代方案是#1,它可以通过两种方式使用。其中第一种可能更具可读性,因为预期的行为非常明确:

var array2 = [].concat(array1);

第二种方法是:

var array2 = array1.concat();

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

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

Lee Penkman也在评论中指出,如果有机会array1undefined,您可以返回一个空数组,如下所示:

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

或者,对于第二种方法:

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

请注意,您也可以使用slicevar 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();}elsenewObj[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 () {} ];

深度复制技术依赖于三种数组类型

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

深度复制技术

按元素类型的Javascript深度复制技术

基准

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

  • 字面量值数组(type1)
    [ ...myArray ]myArray.splice(0)myArray.slice()myArray.concat()技术可用于仅深度复制具有文字值(布尔值、数字和字符串)的数组;其中slice()在Chrome中具有最高性能,而传播...在Firefox中具有最高性能。

  • 字面量值(type1)和字面量结构(type2)的数组
    JSON.parse(JSON.stringify(myArray))技术可用于深度复制文字值(布尔值、数字、字符串)和文字结构(数组、对象),但不能复制原型对象。

  • 所有数组(type1、type2、type3)

    • Lo-dashcloneDeep(myArray)jQueryextend(true, [], myArray)技术可用于深度复制所有数组类型。其中LodashcloneDeep()技术性能最高。
    • 对于那些避免使用第三方库的人,下面的自定义函数将深度复制所有数组类型,性能低于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()和传播...具有最高的性能。

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 abovearr2 = _.cloneDeep(arr1); // Lo-dash.js neededarr2 = jQuery.extend(true, [], arr1); // jQuery.js needed

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

// Empty arrayarr1.length = 0;// Add items from source array to target arrayfor (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;}

感谢James Padolsey提供此功能。

来源:这里

有新引入的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的环境中,使用传播运算符你可以这样做:

var arr1 = ['a','b','c'];var arr2 = [...arr1]; //copy arr1arr2.push('d');
console.log(arr1)console.log(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'];

数组扩展现在是支持所有主流浏览器,但如果您需要较旧的支持,请使用打字脚本或Babel并编译为ES5。

点差的更多信息

从ES2015,

var arr2 = [...arr1];

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

// clonelet x = [1, 2, 3];let y = Array.from(x);console.log({y});
// deep clonelet clone = arr => Array.from(arr, item => Array.isArray(item) ? clone(item) : item);x = [1, [], [[]]];y = clone(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)使用切片方法,切片用于切片数组的一部分,它将切片数组的某些部分而不接触原始,在切片中,如果不指定数组的开始和结束,它将切片整个数组并基本上制作完整的数组副本,所以我们可以很容易地说:

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

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

var arr2 = arr.concat();

4)还有stringify和parse方法,不推荐,但可以是复制数组和对象的简单方法:

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

以下是一些复制的方法:

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

对于包含对象的ES6数组

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

Dan,不需要使用花哨的技巧。您需要做的就是通过这样做来复制arr1。

var arr1 = ['a','b','c'];var arr2 = [];
var arr2 = new Array(arr1);
arr2.push('d');  // Now, arr2 = [['a','b','c'],'d']
console.log('arr1:');console.log(arr1);
console.log('arr2:');console.log(arr2);
// Following did the trick:var arr3 = [...arr1];arr3.push('d');  // Now, arr3 = ['a','b','c','d'];
console.log('arr3:');console.log(arr3);

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

您可以通过以下方式执行此操作:
arr2 = arr1.map(x => Object.assign({}, x));

您可以使用ES6与传播OPeartor,它更简单。

arr2 = [...arr1];

有限制…检查文档传播语法@mozilla

当我们想使用赋值操作符(=)复制数组时,它不会创建副本,它只是将指针/引用复制到数组。例如:

const oldArr = [1,2,3];
const newArr = oldArr;  // now oldArr points to the same place in memory
console.log(oldArr === newArr);  // Points to the same place in memory thus is true
const copy = [1,2,3];
console.log(copy === newArr);  // Doesn't point to the same place in memory and thus is false

通常,当我们转换数据时,我们希望保持初始数据结构(例如数组)不变。我们通过制作数组的精确副本来做到这一点,这样这个数组就可以在初始数组保持不变的情况下进行转换。

复制数组的方法:

const oldArr = [1,2,3];
// Uses the spread operator to spread out old values into the new array literalconst newArr1 = [...oldArr];
// Slice with no arguments returns the newly copied Arrayconst newArr2 = oldArr.slice();
// Map applies the callback to every element in the array and returns a new arrayconst newArr3 = oldArr.map((el) => el);
// Concat is used to merge arrays and returns a new array. Concat with no args copies an arrayconst newArr4 = oldArr.concat();
// Object.assign can be used to transfer all the properties into a new array literalconst newArr5 = Object.assign([], oldArr);
// Creating via the Array constructor using the new keywordconst newArr6 = new Array(...oldArr);
// For loopfunction clone(base) {const newArray = [];for(let i= 0; i < base.length; i++) {newArray[i] = base[i];}return newArray;}
const newArr7 = clone(oldArr);
console.log(newArr1, newArr2, newArr3, newArr4, newArr5, newArr6, newArr7);

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

当嵌套数组时,值将通过引用复制。这是一个如何导致问题的示例:

let arr1 = [1,2,[1,2,3]]
let arr2 = [...arr1];
arr2[2][0] = 5;  // we change arr2
console.log(arr1);  // arr1 is also changed because the array inside arr1 was copied by reference

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

如果你确实想深度克隆一个javascript数组,请将JSON.parseJSON.stringify结合使用,如下所示:

let arr1 = [1,2,[1,2,3]]
let arr2 = JSON.parse(JSON.stringify(arr1)) ;
arr2[2][0] = 5;
console.log(arr1);  // now I'm not modified because I'm a deep clone

复制性能:

那么我们选择哪一个来获得最佳性能。事实证明,最冗长的方法,for循环具有最高的性能。使用for循环进行真正的CPU密集型复制(大/多数组)。

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

源代码性能测试

重要!

这里的大多数答案都适用于特殊情况

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

let clonedArray = [...array]

但是如果你想做深度克隆,请改用:

let cloneArray = JSON.parse(JSON.stringify(array))*

*使用stringify时不会保留(序列化)函数,您将获得没有它们的结果。


对于Lodash用户:

let clonedArray = _.clone(array)留档

let clonedArray = _.cloneDeep(array)留档

快速示例:

  1. 如果数组中的元素是原始类型(字符串、数字等)

var arr1 = ['a','b','c'];// arr1 and arr2 are independent and primitive elements are stored in// different places in the memoryvar arr2 = arr1.slice();arr2.push('d');console.log(arr1); // [ 'a', 'b', 'c' ]console.log(arr2); // [ 'a', 'b', 'c', 'd' ]

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

var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];// arr1 and arr2 are independent and reference's/addresses are stored in different// places in the memory. But those reference's/addresses points to some common place// in the memory.var arr2 = arr1.slice();arr2.pop();      // OK - don't affect arr1 bcos only the address in the arr2 is// deleted not the data pointed by that addressarr2[0].x = 'z'; // not OK - affect arr1 bcos changes made in the common area// pointed by the addresses in both arr1 and arr2arr2[1][0] = 9;	 // not OK - same above reason
console.log(arr1); // [ { x: 'z', y: 'b' }, [ 9, 2 ], [ 3, 4 ] ]console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]

  1. 解决方案2:按元素进行深度复制

var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];arr2 = JSON.parse(JSON.stringify(arr1));arr2.pop();	  // OK - don't affect arr1arr2[0].x = 'z';  // OK - don't affect arr1arr2[1][0] = 9;	  // OK - don't affect arr1
console.log(arr1); // [ { x: 'a', y: 'b' }, [ 1, 2 ], [ 3, 4 ] ]console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]

如果您的数组包含原始数据类型的元素,例如int、char或string等,那么您可以使用其中一个方法来返回原始数组的副本,例如。切片()或. map()或扩展运算符(感谢ES6)。

new_array = old_array.slice()

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

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

但是如果您的数组包含复杂元素,例如对象(或数组)或更多嵌套对象,那么您必须确保您正在复制从顶层到最后一层的所有元素,否则将使用内部对象的引用,这意味着更改new_array中object_elements中的值仍然会影响old_array。您可以将这种在每个级别复制的方法称为制作DEEP COPYold_array

对于深度复制,您可以根据数据类型在每个级别对原始数据类型使用上述方法,或者您可以使用此昂贵的方法(如下所述)进行深度复制,而无需做太多工作。

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

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

let a = [1,2,3];

现在您可以执行以下任何一项来制作数组的副本。

let b = Array.from(a);

let b = [...a];

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

let b = a.slice();

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

现在如果我改变a

a.push(5);

然后,a是[1,2,3,5],但b仍然是[1,2,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'不是按值传递?

在复合值(如Object/Array)上将一个变量传递给另一个变量的行为不同。在copand值上使用一个符号运算符,我们将引用传递给一个对象。这就是为什么在删除/添加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));

例如,我们有一个对象数组,每个单元格在其对象中都有另一个数组域……在这种情况下,如果我们使用切片方法,那么数组字段将复制by 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());

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

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

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