复制数组的值

2123

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

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

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

4
在Chrome 53和Firefox 48中,slicesplice操作的性能表现很好,而新的扩展运算符和Array.from的实现则比较慢。可参考perfjs.fnfo - Pencroff
https://jsperf.com/flat-array-copy - Walle Cyril
对于包含原始字符串的数组,您可以使用 var arr2 = arr1.splice(); 进行深度复制,但是如果您的数组元素包含文字结构(即 []{})或原型对象(即 function () {}new 等),则此技术将无法工作。请参见下面的答案以获取更多解决方案。 - tim-montague
41
2017年了,你可以考虑使用ES6的特性:let arr2 = [...arr1];。详见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator。 - Hinrich
2
当你声明 a = b; 时,实际上是告诉程序在随机访问内存中指向同一个符号链接。当更改此符号链接处的值时,会影响到 ab... 因此,如果你使用扩展运算符 a= [...b];,程序将创建一个额外的符号链接到随机访问内存中的不同位置,然后你可以独立地操作 ab - 71GA
显示剩余3条评论
40个回答

3124
使用这个:

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

let newArray = oldArray.slice();

console.log({newArray});

基本上,slice() 操作会克隆整个数组并返回一个指向新数组的引用。

还要注意:

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

字符串和数字等基本数据类型是不可变的,因此无法更改字符串或数字。


9
关于性能,以下 JSPerf 测试实际上表明 var arr2 = arr1.slice() 和 var arr2 = arr1.concat() 一样快。JSPerf链接:http://jsperf.com/copy-array-slice-vs-concat/5 和 http://jsperf.com/copy-simple-array。http://jsperf.com/array-copy/5 的结果有点让我惊讶,以至于我怀疑测试代码是否有效。 - Cohen
117
虽然这已经获得了大量的点赞,但它仍然值得再次点赞,因为它正确地描述了JS中的参考文献,这在某种程度上是罕见的,不幸的是。 - Wayne
38
你会仅仅为了可读性而添加整个库吗?真的吗?如果我很在意可读性,我只会添加注释。见:var newArray = oldArray.slice(); //将oldArray克隆到newArray。 - dudewad
13
@GáborImre 我理解,当然。但是,在我的看法中,通过包含整个库来回答一个特定的工程问题并不是有益的,它会导致设计膨胀。我经常看到开发人员这样做,然后你最终会得到一个包含整个框架以替代编写单个函数的项目。但这只是我的个人看法。 - dudewad
14
教训:不要混淆.slice().splice(),后者会给你一个空数组,两者有很大的区别。 - crazypeter
显示剩余12条评论

671

在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

  • 仅包含字面值的数组(类型1)
    可以使用 [ ...myArray ]myArray.splice(0)myArray.slice()myArray.concat() 技术对仅包含字面值(布尔值、数字和字符串)的数组进行深拷贝;其中,在 Chrome 中,slice() 的性能最高,在 Firefox 中, spread ... 的性能最高。

  • 包含字面值(类型1)和字面结构的数组(类型2)
    可以使用 JSON.parse(JSON.stringify(myArray)) 技术进行字面值(布尔值、数字、字符串)和字面结构(数组、对象)的深拷贝,但不能处理原型对象。

  • 所有类型的数组(类型1、类型2、类型3)

    • 可以使用 Lo-dashcloneDeep(myArray)jQueryextend(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

3
许多方法效果不佳。 使用赋值运算符意味着您必须重新分配arr1的原始文本值。这种情况非常罕见。 使用splice会抹掉arr1,所以这根本不是复制。 如果数组中的任何值是函数或具有原型(如日期),则使用JSON将失败。 - Dancrumb
1
为什么是splice(0)?不应该是slice()吗?我认为它不应该修改原始数组,而splice会这样做。@JamesMontagne - helpse
2
splice函数将创建指向原始数组元素的指针(浅拷贝)。对于数组中的数字或字符串元素,splice(0)将为它们分配新的内存(深拷贝),并为所有其他元素类型创建指针(浅拷贝)。通过将起始值设置为零传递给splice函数方法,它不会从原始数组中剪切任何元素,因此不会修改它。 - tim-montague
2
实际上,只有一种类型的数组:一个由“某些东西”组成的数组。[0,"1",{2:3},function random() {return 4;}, [[5,6,7],[8,9,10],[11,12,13]]] 和其他任何数组之间没有区别。 - wizzwizz4
1
在我看来,这些都很难读懂。我不明白为什么有人要克隆一个数组。使用 x.clone() 有什么问题吗? - Jonny
显示剩余8条评论

219

你可以使用数组展开语法...来复制数组。

const itemsCopy = [...items];

如果想要创建一个新的数组并将现有数组作为其中一部分:

const newArray = [...existingArray, newItem1, newItem2];

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

数组扩展现在在所有主要浏览器中得到支持,但如果您需要旧版支持,请使用TypeScript或Babel并编译为ES5。

更多有关扩展的信息


7
这对深拷贝无效。JavaScript中的数组深度克隆 - SNag
1
请注意,这也会在原始数组中创建“undefined”的新条目。这可能会破坏您的代码。 - Krisztián Balla

166

不需要 jQuery... 工作示例

var arr2 = arr1.slice()

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

重要的是要注意,对于基本类型(字符串,数字等),它将按预期工作,并且还要说明引用类型的预期行为...

如果您有一个引用类型的数组,比如Object类型。该数组将会被复制,但两个数组都包含指向相同Object的引用。因此,在这种情况下,虽然数组实际上是被复制了的,但似乎像是通过引用复制了数组。


18
不,这不会是一个深度拷贝。 - jondavidjohn
1
试一试;var arr2 = JSON.stringify(arr1); arr2 = JSON.parse(arr2); - pradeep1991singh
4
这个答案和被采纳的答案有什么区别? - Isaac Pak
在您提供的示例中,控制台出现错误“TypeError: window.addEvent不是一个函数”。 - Ravi Sharma
@IsaacPak 这个问题在那之前已经回答了2分钟。 - brc-dd
var arr2 = arr1.slice(0); 这个方法只适用于简单的数组。 如果您有复杂的数组,比如对象数组,那么您必须使用另一种方式,例如: const arr2 = JSON.parse(JSON.stringify(arr1)); - Eyni Kave

84

在尝试了多种方法之后,我是这样做的:

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

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

另外,显然这不会克隆事件和函数,但好消息是你只需要一行代码就可以完成,并且可以用于任何类型的对象(数组、字符串、数字、对象等)。


6
这是最好的一个。我很久以前就使用了同样的方法,并认为老式递归循环已经没有意义了。 - Vladimir Kharlampidi
2
请注意,此选项无法很好地处理类似图形的结构:在存在循环时会崩溃,并且不保留共享引用。 - Ruben
3
这对于像Date这样有原型的任何对象都会失败。此外, undefined被转换为null - Dancrumb
10
有没有人勇敢地评论一下将对象序列化为文本,然后再解析回来的过程中 CPU 和内存效率极低的问题? - Lawrence Dol
1
这是一次深度复制。问题并没有要求这样做。虽然它很有用,但它与其他答案做了不同的事情 - wizzwizz4
显示剩余2条评论

77

使用 concat 作为 slice 的替代方案,它有两种用法。第一种用法可能更易读,因为预期的行为非常明确:

var array2 = [].concat(array1);
第二种方法是:
var array2 = array1.concat();

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

这种方法的原理是concat方法创建一个新的数组,该数组由调用它的对象中的元素,以及传递给它作为参数的任何数组中的元素组成。因此,当没有传递参数时,它只会复制该数组。

同样在评论中,Lee Penkman 指出,如果有可能array1undefined,则可以按以下方式返回一个空数组:

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

或者,对于第二种方法:

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

请注意您也可以使用 slice 来实现这个功能:var array2 = (array1 || []).slice();.


31
实际上你也可以这样做:var array2 = array1.concat(); 这样做在性能方面会更快。 (JSPerf: http://jsperf.com/copy-simple-array 和 http://jsperf.com/copy-array-slice-vs-concat/5) - Cohen
5
值得注意的是,如果array1不是一个数组,那么[].concat(array1)将返回[array1],例如如果它是未定义的,则会得到[undefined]。有时我会使用var array2 = [].concat(array1 || []); - lee penkman

29

重要提示!

这里大部分答案都适用于特定情况

如果你不关心深层嵌套的对象和属性,可以使用以下代码(ES6):

let clonedArray = [...array]

但是如果你想进行深度克隆,可以使用以下代码:

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

*由于序列化的原因,函数将不会被保留,你将得到没有它们的结果。


对于lodash用户:

let clonedArray = _.clone(array) 文档

let clonedArray = _.cloneDeep(array) 文档


24

我个人认为Array.from是一种更易读的解决方案。顺便提一下,要注意它的浏览器支持情况。

// clone
let x = [1, 2, 3];
let y = Array.from(x);
console.log({y});

// deep clone
let clone = arr => Array.from(arr, item => Array.isArray(item) ? clone(item) : item);
x = [1, [], [[]]];
y = clone(x);
console.log({y});


2
是的,这非常易读。使用.slice()解决方案完全不直观。感谢您提供这个。 - Banago

22

在处理数字或字符串等简单数据类型时,一些提到的方法可以很好地工作,但是当数组包含其他对象时,这些方法就会失败。当我们尝试将任何对象从一个数组传递到另一个数组时,它会作为引用而不是对象传递。

请将以下代码添加到您的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()

它会起作用。


2
当我将这段代码添加到我的页面时,出现了“Uncaught RangeError: Maximum call stack size exceeded”错误。 - sawe
1
抱歉,如果未声明arr1,则此错误会在Chrome中发生。因此,我复制粘贴了上面的代码,并且出现了错误,但是,如果我声明数组arr1,则不会出现错误。您可以通过在arr2正上方声明arr1来改进答案,我看到有很多“我们”没有意识到我们必须声明arr1(部分原因是当我评估您的答案时,我很匆忙,需要一些“只需工作”的东西)。 - sawe
7
@Jason,但这些对象仍指向同一对象,因此更改其中一个对象将影响另一个对象。http://jsfiddle.net/k525g/1/ - Samuel
优秀的代码。我有一个问题,我尝试将一个数组复制到另一个数组中,像这样 var arr1 = new Array() 然后 var arr2 = arr1; 如果我在arr2中更改了某些内容,则arr1也会发生更改。但是,如果我使用您创建的克隆原型,它实际上会创建该数组的完全新实例,换句话说,它会复制它。那么这是浏览器的问题吗?还是javascript默认情况下使用指针将两个变量设置为一个指向另一个变量,当某人执行var arr2 = arr1时,为什么整数变量不会发生这种情况?请参见http://jsfiddle.net/themhz/HbhtA/ - themhz
这是唯一能与全局变量一起使用的方法,splice和concat不适用于全局变量。 - Grumpy
显示剩余2条评论

22

从 ES2015 开始,

var arr2 = [...arr1];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接