在JavaScript中按值复制数组

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

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

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

用这个:

 var newArray = oldArray.slice(); 

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

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

string和数字等原始字符是不可变的,所以对string或数字的修改是不可能的。

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

三种types的元素

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

 // 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 () {}" 

从这些元素我们可以创build三种types的数组。

 // 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 () {}]; 

深度复制技术取决于三种数组types

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

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

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

  • 字面值(types1)和文字结构(types2)
    JSON.parse(JSON.stringify(myArray))技术可用于深度复制文字值(布尔,数字,string)和文字结构(数组,对象),但不能用于原型对象。

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

     // Warning: In Node.js this solution is problematic; // any `null` elements in the deep copied array will // be converted to empty object literals `{}`. function copy(o) { var output, v, key; output = Array.isArray(o) ? [] : {}; for (key in o) { v = o[key]; output[key] = (typeof v === "object") ? copy(v) : v; } return output; } 

所以要回答这个问题

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

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

回答

因为arr1是字面值(布尔值,数字或string)的数组,所以可以使用上面讨论的任何深度复制技术,其中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 

没有jQuery需要… 工作示例

 var arr2 = arr1.slice() 

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

重要的是要注意,它将按照原始types(string,数字等)的预期工作,并解释参考types的预期行为。

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

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

const itemsCopy = [...items];

另外,如果想要创build一个新的数组与现有的一部分:

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

现在所有主stream浏览器都支持数组传播,但是如果您需要更老的支持,请使用打字稿或babel并编译到ES5。

点差的更多信息

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

 var array2 = [].concat(array1); 

第二种方法是:

 var array2 = array1.concat(); 

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

这种方式的工作方式是, concat方法创build一个新的数组,其中包含调用该对象的对象中的元素,接着是作为parameter passing给它的任何数组的元素。 所以当没有parameter passing时,它只是复制数组。

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

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

或者,对于第二种方法:

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

注意,你也可以用slice来做到这一点: var array2 = (array1 || []).slice();

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

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

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

此外,这显然不会克隆事件和function,但好处是你可以在一行中做到这一点,它可以用于任何types的对象(数组,string,数字,对象…)

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

在您的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() 

它会工作。

从ES2015开始,

 var arr2 = [...arr1]; 

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

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

同样的东西去对象的数组,他们将被引用复制,你必须手动复制它们

如果您处于ECMAScript 6的环境中,那么使用Spread Operator可以这样做:

 var arr1 = ['a','b','c']; var arr2 = [...arr1]; //copy arr1 arr2.push('d'); console.log(arr1) console.log(arr2) 
 <script src="http://www.wzvang.com/snippet/ignore_this_file.js"></script> 

我个人认为Array.from是一个更可读的解决scheme。 顺便提一下,只要小心它的浏览器支持。

 //clone let x = [1,2,3]; let y = Array.from(x); //deep clone let clone = arr => Array.from(arr,item => Array.isArray(item) ? clone(item) : item); let x = [1,[],[[]]]; let y = clone(x); 

在我的情况下,我需要确保数组保持完好,所以这对我工作:

 // 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]); } 

复制multidimensional array/对象:

 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的这个function。

来源: 这里

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

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

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

正如我们所知道的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)也是string化和parsing方法,不推荐,但是可以简单地复制数组和对象:

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

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

 var arr2 = Array.from(arr); 

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

 let arr2 = [...arr]; 

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

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

这是一个变种:

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

参考: 这里

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

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