将 JavaScript 中的数组复制到另一个数组时:
var arr1 = ['a','b','c'];
var arr2 = arr1;
arr2.push('d'); //Now, arr1 = ['a','b','c','d']
我意识到 arr2
指的是与 arr1
相同的数组,而不是一个新的独立数组。如何复制数组以获得两个独立的数组?
let arr2 = [...arr1];
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
a = b;
时,您实际上是在告诉程序在两种情况下都指向随机存取存储器中的相同符号链接。当这个符号链接的值改变时,它会影响 a
和 b
...所以如果你使用扩展运算符 a= [...b];
程序将创建一个额外的符号链接到随机存取存储器中的不同位置,你可以然后独立操作 a
和 b
。
用这个:
让 oldArray = [1, 2, 3, 4, 5];让 newArray = oldArray.slice();控制台.log({newArray});
基本上,slice()
操作会克隆数组并返回对新数组的引用。
另请注意:
对于引用、字符串和数字(而不是实际对象),slice()
将对象引用复制到新数组中。原始数组和新数组都引用同一个对象。如果引用的对象发生更改,则更改对新数组和原始数组都可见。
字符串和数字等原语是不可变的,因此不可能更改字符串或数字。
在 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://i.stack.imgur.com/UL33O.png
基准
https://www.measurethat.net/Benchmarks/Show/17502/0/deep-copy-comparison
文字值数组 (type1) [ ...myArray ]、myArray.splice(0)、myArray.slice() 和 myArray.concat() 技术可用于深度复制具有文字值的数组(布尔值、数字, 和字符串) 仅;其中 slice() 在 Chrome 中性能最高,而 spread ... 在 Firefox 中性能最高。
文字值数组 (type1) 和文字结构 (type2) JSON.parse(JSON.stringify(myArray)) 技术可用于深度复制文字值(布尔值、数字、字符串)和文字结构(数组、对象),但不是原型对象。
所有数组 (type1, type2, type3) Lo-dash cloneDeep(myArray) 或 jQuery extend(true, [], myArray) 技术可用于深度复制所有数组类型。 Lodash cloneDeep() 技术具有最高性能的地方。而对于那些避免使用第三方库的人来说,下面的自定义函数将对所有数组类型进行深度复制,性能低于cloneDeep(),但性能高于extend(true)。
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()
和展开 ...
具有最高性能。
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
arr1
的原始文字值。这是非常罕见的情况。使用 splice
会删除 arr1
,因此这根本不是副本。如果数组中的任何值是函数或具有原型(例如 Date
),则使用 JSON
将失败。
[0,"1",{2:3},function random() {return 4;}, [[5,6,7],[8,9,10],[11,12,13]]]
与任何其他数组没有区别。
x.clone()
有什么问题?
您可以使用数组扩展 ...
来复制数组。
const itemsCopy = [...items];
此外,如果要创建一个新数组,其中现有数组是其中的一部分:
var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];
数组扩展现在是 supported in all major browsers,但如果您需要较旧的支持,请使用 typescript 或 babel 并编译为 ES5。
不需要 jQuery...Working Example
var arr2 = arr1.slice()
这会将数组从起始位置 0
复制到数组末尾。
重要的是要注意它对于原始类型(字符串、数字等)将按预期工作,并解释引用类型的预期行为......
如果您有一个引用类型数组,例如类型 Object
。 将复制数组,但两个数组都将包含对相同 Object
的引用。因此,在这种情况下,即使数组 实际上是复制的,但数组似乎是通过引用复制的。
var arr2 = JSON.stringify(arr1); arr2 = JSON.parse(arr2);
这就是我在尝试了许多方法后所做的:
var newArray = JSON.parse(JSON.stringify(orgArray));
这将创建一个与第一个不相关的新深副本(不是浅副本)。
同样,这显然不会克隆事件和函数,但是你可以在一行中完成它的好处,它可以用于任何类型的对象(数组、字符串、数字、对象......)
Date
之类的东西,或者实际上,任何具有原型的东西,这也会失败。此外,undefined
被转换为 null
。
slice
的替代品是 concat
,它有两种使用方式。其中第一个可能更具可读性,因为预期的行为非常清楚:
var array2 = [].concat(array1);
第二种方法是:
var array2 = array1.concat();
Cohen(在评论中)指出后一种方法has better performance。
其工作方式是 concat
方法创建一个新数组,该数组由调用它的对象中的元素以及作为参数传递给它的任何数组的元素组成。因此,当没有传递任何参数时,它只是复制数组。
Lee Penkman 也在评论中指出,如果 array1
有可能是 undefined
,您可以返回一个空数组,如下所示:
var array2 = [].concat(array1 || []);
或者,对于第二种方法:
var array2 = (array1 || []).concat();
请注意,您也可以使用 slice
:var array2 = (array1 || []).slice();
执行此操作。
[].concat(array1)
返回 [array1]
,例如,如果它未定义,您将得到 [undefined]
。我有时会var array2 = [].concat(array1 || []);
重要的!
这里的大多数答案都适用于特定情况。
如果您不关心深层/嵌套对象和道具使用(ES6):
let clonedArray = [...array]
但如果你想进行深度克隆,请改用:
let cloneArray = JSON.parse(JSON.stringify(array))
*
*函数在使用 stringify 时不会被保留(序列化),没有它们你会得到结果。
对于 lodash 用户:
let clonedArray = _.clone(array)
documentation
和
let clonedArray = _.cloneDeep(array)
documentation
我个人认为 Array.from 是一个更具可读性的解决方案。顺便说一句,请注意它的浏览器支持。
// 克隆 let x = [1, 2, 3];让 y = Array.from(x);控制台.log({y}); // 深度克隆 let clone = arr => Array.from(arr, item => Array.isArray(item) ? clone(item) : item); x = [1, [], [[]]]; y = 克隆(x);控制台.log({y});
.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()
它会起作用的。
.slice()
仍然可以正常工作:jsfiddle.net/edelman/k525g
从 ES2015 开始,
var arr2 = [...arr1];
如果您在 ECMAScript 6 的环境中,使用 Spread Operator 您可以这样做:
var arr1 = ['a','b','c']; var arr2 = [...arr1]; //复制 arr1 arr2.push('d'); console.log(arr1) console.log(arr2)
原始值始终按其值传递(复制)。然而,复合值是通过引用传递的。
那么我们如何复制这个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` 不按值传递?
在诸如对象/数组之类的复合值上将一个变量传递给另一个变量的行为不同。在 copand 值上使用 asign 运算符,我们将引用传递给对象。这就是为什么在删除/添加 arr 元素时两个数组的值都会发生变化的原因。
例外:
arrCopy[1] = 'adding new value this way will unreference';
当您为变量分配新值时,您正在更改引用本身并且它不会影响原始对象/数组。
添加到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]);
对象数组也是如此,它们将通过引用复制,您必须手动复制它们
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.push(5);
然后, a 是 [1,2,3,5] 但 b 仍然是 [1,2,3] 因为它有不同的参考。
但我认为,在上述所有方法中 Array.from 更好,主要用于复制数组。
当有很多答案时,您必须对这个问题使用最佳实践。
我建议您使用数组扩展……来复制数组。
var arr1 = ['a','b','c'];
var arr2 = […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)还有联系方法,这是用于合并两个数组,但我们可以只指定一个数组,然后这基本上会复制新的联系数组中的值:
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];
丹,没必要用花哨的把戏。您需要做的就是通过这样做来复制 arr1 。
var arr1 = ['a','b','c']; var arr2 = []; var arr2 = 新数组(arr1); arr2.push('d'); // 现在,arr2 = [['a','b','c'],'d'] console.log('arr1:');控制台.log(arr1); console.log('arr2:');控制台.log(arr2); // 下面是诀窍: var arr3 = [...arr1]; arr3.push('d'); // 现在,arr3 = ['a','b','c','d']; console.log('arr3:');控制台.log(arr3);
现在 arr1
和 arr2
是存储在不同堆栈中的两个不同的数组变量。 Check this out on jsfiddle。
var arr2 = [arr1];
)。
document.write(arr2)
更改为 console.log(arr2)
以便 nested array 结构显示并更好说明来自@Timothy003 的正确评论。不过,var arr3 = [...arr1]
成功了。运行代码片段以查看结果。 (document.write(arr2)
的输出有点误导,因此我不怪你)。
在我的特殊情况下,我需要确保数组保持完整,所以这对我有用:
// 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;
}
感谢 James Padolsey 提供此功能。
来源:Here
如果您的数组包含原始数据类型的元素,例如 int、char 或 string 等,那么您可以使用其中一种返回原始数组副本的方法,例如 .slice() 或 .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。您可以将这种在每个级别复制的方法称为对 old_array 进行深度复制。
对于深度复制,您可以根据数据类型在每个级别使用上述方法用于原始数据类型,或者您可以使用这种昂贵的方法(如下所述)进行深度复制而无需做太多工作。
var new_array = JSON.parse(JSON.stringify(old_array));
您可以根据自己的要求使用许多其他方法。我只提到了其中的一些,以大致了解当我们尝试按值将一个数组复制到另一个数组时会发生什么。
如果要创建对象或数组的新副本,则必须显式复制对象的属性或数组的元素,例如:
var arr1 = ['a','b','c'];
var arr2 = [];
for (var i=0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
您可以在 Google 上搜索有关不可变原始值和可变对象引用的更多信息。
当我们想使用赋值运算符 ( =
) 复制一个数组时,它不会创建一个副本,它只是将指针/引用复制到数组。例如:
常量 oldArr = [1,2,3];常量 newArr = oldArr; // 现在 oldArr 指向内存中的相同位置 console.log(oldArr === newArr); // 指向内存中的同一个地方因此是真的 const copy = [1,2,3]; console.log(复制 === newArr); // 不指向内存中的同一个地方,因此是错误的
通常,当我们转换数据时,我们希望保持初始数据结构(例如数组)完好无损。我们通过制作我们的数组的精确副本来做到这一点,以便可以在原始数组保持不变的情况下对其进行转换。
复制数组的方法:
常量 oldArr = [1,2,3]; // 使用展开运算符将旧值展开到新数组字面量中 const newArr1 = [...oldArr]; // 不带参数的切片返回新复制的数组 const newArr2 = oldArr.slice(); // Map 将回调应用于数组中的每个元素并返回一个新数组 const newArr3 = oldArr.map((el) => el); // concat 用于合并数组并返回一个新数组。没有 args 的 concat 复制一个数组 const newArr4 = oldArr.concat(); // Object.assign 可用于将所有属性转移到一个新的数组字面量中 const newArr5 = Object.assign([], oldArr); // 通过 Array 构造函数使用 new 关键字创建 const newArr6 = new Array(...oldArr); // For 循环函数 clone(base) { const newArray = []; for(let i= 0; i < base.length; i++) { newArray[i] = base[i]; } 返回新数组; } const newArr7 = clone(oldArr); console.log(newArr1, newArr2, newArr3, newArr4, newArr5, newArr6, newArr7);
嵌套数组或对象时要小心!:
当数组嵌套时,值通过引用复制。这是一个如何导致问题的示例:
让 arr1 = [1,2,[1,2,3]] 让 arr2 = [...arr1]; arr2[2][0] = 5; // 我们改变 arr2 console.log(arr1); // arr1 也改变了,因为 arr1 中的数组是通过引用复制的
因此,当您要复制的数组中存在对象或数组时,请勿使用这些方法。即,仅对基元数组使用这些方法。
如果您确实想要深度克隆 javascript 数组,请结合使用 JSON.parse
和 JSON.stringify
,如下所示:
让 arr1 = [1,2,[1,2,3]] 让 arr2 = JSON.parse(JSON.stringify(arr1)) ; arr2[2][0] = 5;控制台.log(arr1); // 现在我没有被修改,因为我是一个深度克隆
复制性能:
所以我们选择哪一个以获得最佳性能。事实证明,最冗长的方法,for
循环具有最高的性能。使用 for
循环进行真正的 CPU 密集型复制(大/许多数组)。
之后,.slice()
方法也有不错的性能,而且更简洁,程序员更容易实现。我建议使用 .slice()
来复制 CPU 密集度不高的数组。如果不需要深度克隆并且性能是一个问题,还应避免使用 JSON.parse(JSON.stringify(arr))
(大量开销)。
var arr2 = arr1.slice(0);
这种方式只适用于简单的数组。
如果您有类似对象数组的复杂数组,那么您必须使用其他解决方案,例如:
const arr2 = JSON.parse(JSON.stringify(arr1));
例如,我们有一个对象数组,每个单元格在其对象中都有另一个数组字段......在这种情况下,如果我们使用 slice 方法,那么数组字段将通过 Ref 复制,这意味着这些字段更新将对原始数组产生相同的影响元素和字段。
使用 jQuery 深拷贝可以如下进行:
var arr2 = $.extend(true, [], arr1);
您还可以使用 ES6 扩展运算符来复制 Array
var arr=[2,3,4,5];
var copyArr=[...arr];
这里还有一些复制方法:
常量数组 = [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 }));
}
快速示例:
如果数组中的元素是原始类型(字符串、数字等)
var arr1 = ['a','b','c']; // arr1 和 arr2 是独立的,原始元素存储在 // 内存中的不同位置 var arr2 = arr1.slice(); arr2.push('d');控制台.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(); // OK - 不影响 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 - 不影响 arr1 arr2[0].x = 'z'; // OK - 不影响 arr1 arr2[1][0] = 9; // OK - 不影响 arr1 console.log(arr1); // [ { x: 'a', y: 'b' }, [ 1, 2 ], [ 3, 4 ] ] console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]
.slice()
与.splice()
混淆,后者会给您一个空数组。巨大差距。