题 在JavaScript中按值复制数组


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

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

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


1345
2017-09-20 13:38


起源


看起来目前在Chrome 53和Firefox 48中我们有很酷的性能 slice 和 splice 运营和新的传播运营商和 Array.from 执行速度慢得多。看着 perfjs.fnfo - Pencroff
jsben.ch/#/wQ9RU <=此基准测试概述了复制数组的不同方法 - EscapeNetscape
jsperf.com/flat-array-copy - Walle Cyril
对于这个数组(包含原始字符串的数组),您可以使用 var arr2 = arr1.splice(); 深度复制,但如果数组中的元素包含文字结构,则此技术将无效(即 [] 要么 {})或原型对象(即 function () {}, new等)。请参阅下面的答案以获取更多解决方 - tfmontague
这是2017年,因此您可以考虑使用ES6功能: let arr2 = [...arr1];  developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... - Hinrich


答案:


用这个:

var newArray = oldArray.slice();

基本上, 片() operation克隆数组并返回对新数组的引用。另请注意:

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

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


2161
2017-09-20 13:41



关于性能,以下jsPerf测试实际上表明var arr2 = arr1.slice()与var arr2 = arr1.concat()一样快; JSPerf: jsperf.com/copy-array-slice-vs-concat/5 和 jsperf.com/copy-simple-array 。的结果 jsperf.com/array-copy/5 有点让我感到惊讶,我想知道测试代码是否有效。 - Cohen
虽然这已经收到了大量的赞成票,但它应该得到另一个,因为它恰当地描述了JS中的引用,遗憾的是,它很少见。 - Wayne Burkett
@GáborImre为了便于阅读,您是否添加了整个库?真?如果我担心可读性,我会添加评论。请参阅:var newArray = oldArray.slice(); //将oldArray克隆为newArray - dudewad
@GáborImre我明白了。但是在我看来,通过包含整个库来回答特定的工程问题是没有用的,这是设计膨胀。我看到开发人员做了很多,然后你最终得到了一个包含整个框架的项目,以替换必须编写单个函数。不过只是我的M.O. - dudewad
@dudewad一般来说你是对的。在这种情况下,我的整个JS经验建议使用这个lib 几乎每个项目。 Lodash用于数组或对象,如Moment for dates,如Express for NodeJS服务器,如Angular或React for frontend。 - Gábor Imre


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

三种类型的元素

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

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

从这些元素中我们可以创建三种类型的数组。

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

深层复制技术取决于三种阵列类型

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

Javascript deep copy techniques by element types

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

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

  • 所有数组(type1,type2,type3)
    jQuery $.extend(myArray) 技术可用于深度复制所有数组类型。像这样的图书馆 下划线 和 罗破折号 提供类似的深拷贝功能 jQuery的  $.extend()但性能较低。更令人惊讶的是, $.extend() 具有比...更高的性能 JSON.parse(JSON.stringify(myArray)) 技术 http://jsperf.com/js-deep-copy/15
    对于那些回避第三方库(如jQuery)的开发人员,您可以使用以下自定义函数;它具有比$ .extend更高的性能,并深度复制所有数组。

    function copy(o) {
      var output, v, key;
      output = Array.isArray(o) ? [] : {};
      for (key in o) {
        v = o[key];
        output[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
      }
      return output;
    }  
    

所以回答这个问题......

 

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

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

回答 

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

365
2018-05-08 08:36



Saket的答案不使用 splice 它用 slice。非常不一样 - James Montagne
其中许多方法效果不佳。使用赋值运算符意味着您必须重新分配原始的文字值 arr1。这种情况非常罕见。运用 splice 湮灭 arr1,所以这根本不是副本。运用 JSON 如果数组中的任何值是函数或具有原型(例如a),则会失败 Date)。 - Dancrumb
我认为这应该是问题的正确答案。切片,concat有问题 - Manu M
为什么拼接(0)?不应该是slice()吗?我认为它不应该修改原始数组,这是拼接所做的。 @JamesMontagne - helpse
splice将创建指向原始数组中元素的指针(浅拷贝)。 splice(0)将为数组中的元素(数字或字符串)分配新内存(深层副本),并为所有其他元素类型(浅层副本)创建指针。通过将起始值0传递给splice函数方法,它不会拼接原始数组中的任何元素,因此不会对其进行修改。 - tfmontague


不需要jQuery ...... 工作实例

var arr2 = arr1.slice()

这将从起始位置复制阵列 0 通过数组的末尾。

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

如果您有一个引用类型数组,请说类型 Object。阵列  被复制,但两个数组都将包含对它们的引用 Object的。所以在这种情况下,即使数组也可能通过引用复制数组 实际上是复制了。


146
2017-09-20 13:39



谢谢你的例子。我很好奇,这种复制被称为深层复制吗? - ayjay
不,这不是一个深刻的副本。 - jondavidjohn


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

const itemsCopy = [...items];

此外,如果想要创建一个新数组,其中现有数组是其中的一部分:

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

数组传播现在 支持所有主流浏览器 但如果您需要较旧的支持,请使用typescript或babel并编译为ES5。

有关点差的更多信息


138
2017-07-03 14:46



这应该被赞成 - SobiborTreblinka
万岁为ES6答案。 - reid
与全能扩展运算符相比,该片应该被弃用! - Chang
啊,这就是我想要的。 - Hinrich
只是一个注释,这只适用于原始类型,对象数组将是浅拷贝 - gajo


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

var array2 = [].concat(array1);

第二种方法是:

var array2 = array1.concat();

科恩(在评论中)指出了后一种方法 有更好的表现

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

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

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

或者,对于第二种方法:

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

请注意,您也可以这样做 slicevar array2 = (array1 || []).slice();


67
2017-10-18 00:11



实际上你也可以这样做:var array2 = array1.concat();性能方面要快得多。 (JSPerf: jsperf.com/copy-simple-array 和 jsperf.com/copy-array-slice-vs-concat/5 - Cohen
值得注意的是,如果array1不是一个数组那么 [].concat(array1) 回报 [array1] 例如如果它未定义你会得到 [undefined]。我有时会这样做 var array2 = [].concat(array1 || []); - lee penkman
Concat不会创建对象的引用,但会创建一个新数组。这就是我需要的 - Shyamal Parikh


这是我在尝试了许多方法后的方法:

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

这将创建一个与第一个无关的新深拷贝(不是浅拷贝)。

此外,这显然不会克隆事件和函数,但你可以在一行中做到这一点,它可以用于任何类型的对象(数组,字符串,数字,对象......)


40
2018-04-23 13:32



这是最好的一个。很久以前我使用相同的方法,并认为在旧学校的递归循环中没有更多的意义 - Vladimir Kharlampidi
请注意,此选项不能处理类似图形的结构:存在周期时崩溃,并且不保留共享引用。 - Ruben
这也失败了 Date或者实际上,任何有原型的东西。此外, undefineds转换为 null秒。 - Dancrumb
没有人勇敢地评论序列化到文本然后解析回对象的CPU和内存的总体低效率吗? - Lawrence Dol
这个解决方案是唯一有效的解决方案。使用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()

它会工作。


17
2017-12-12 16:22



当我将此代码添加到我的页面'Uncaught RangeError:超出最大调用堆栈大小'时,我收到此错误 - sawe
你在哪个浏览器上看到这个错误? - sarvesh singh
我很抱歉,如果未声明arr1,则会在chrome中发生此错误。所以我复制粘贴上面的代码,然后我得到错误,但是,如果我声明数组arr1,那么我没有得到错误。你可以通过在arr2上面声明arr1来改善答案,我看到有很多'我们'在那里没有认识到我们必须申报arr1(部分原因是当我评估你的答案时,我很匆忙并需要“正常工作”的东西 - sawe
.slice() 即使你的数组​​中有对象,仍然可以正常工作: jsfiddle.net/edelman/k525g - Jason
@Jason但是对象仍然指向同一个对象,所以改变一个对象会改变另一个对象。 jsfiddle.net/k525g/1 - Samuel


从ES2015开始,

var arr2 = [...arr1];

14
2017-12-24 07:14