在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,我们可以使用各种技术来进行深度复制。
-
字面值数组(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的数组,比如Object
types。 该数组将被复制,但是这两个数组都将包含对同一个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指出,如果array1
是undefined
,你可以返回一个空数组,如下所示:
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);