按值复制数组

将 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()操作将克隆该数组并返回对新数组的引用。

另请注意:

对于引用,字符串和数字(而不是实际对象), 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 () {}];

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

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

按元素类型的Javascript深层复制技术

  • 文字值数组(类型 1)
    [...myArray]myArray.splice(0)myArray.slice()myArray.concat()技术只能用于深度复制具有文字值(布尔值,数字和字符串)的数组; Spread 运算符[...myArray]的性能最佳( https://measurethat.net/Benchmarks/Show/4281/0/spread-array-performance-vs-slice-splice-concat )。

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

  • 所有数组(type1,type2,type3)
    jQuery $.extend(myArray)技术可用于深度复制所有数组类型。像UnderscoreLo-dash这样的库提供与jQuery $.extend()类似的深层复制功能,但性能较低。更令人惊讶的是, $.extend()性能比JSON.parse(JSON.stringify(myArray))技术http://jsperf.com/js-deep-copy/15更高。
    对于那些避开第三方库(例如 jQuery)的开发人员,您可以使用以下自定义函数;其性能比 $ .extend 更高,并且可以深度复制所有数组。

function copy(aObject) {
  if (!aObject) {
    return aObject;
  }

  let v;
  let bObject = Array.isArray(aObject) ? [] : {};
  for (const k in aObject) {
    v = aObject[k];
    bObject[k] = (typeof v === "object") ? copy(v) : v;
  }

  return bObject;
}

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

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

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

回答

因为arr1是文字值(布尔值,数字或字符串)的数组,所以您可以使用上面讨论的任何深层复制技术,其中散布运算符...具有最高的性能。

// Highest performance for deep copying literal values
arr2 = [...arr1];

// Any of these techniques will deep copy literal values as well,
//   but with lower performance.
arr2 = arr1.slice();
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

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

const itemsCopy = [...items];

另外,如果要创建一个包含现有数组的新数组,请执行以下操作:

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

现在,所有主要浏览器支持数组扩展,但是如果您需要较早的支持,请使用打字稿或 babel 并编译为 ES5。

有关价差的更多信息

不需要 jQuery ... 工作示例

var arr2 = arr1.slice()

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

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

如果您具有引用类型的数组,请说类型为Object 。该数组被复制,但是两个数组都将包含对相同Object的引用。因此,在这种情况下,即使实际复制了数组,也似乎是通过引用复制了该数组

slice的替代方法是concat ,可以有两种使用方式。由于预期的行为非常明确,因此第一个可能更易读:

var array2 = [].concat(array1);

第二种方法是:

var array2 = array1.concat();

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

这种工作方式是concat方法创建一个新数组,该数组由调用该对象的对象中的元素组成,然后是作为参数传递给它的任何数组的元素。因此,当不传递任何参数时,它仅复制数组。

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

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

或者,对于第二种方法:

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

注意,您也可以使用slice来执行此操作: var array2 = (array1 || []).slice();

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

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

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

同样,这显然不会克隆事件和函数,但是一件好事是您可以在一行中完成它,并且可以将其用于任何类型的对象(数组,字符串,数字,对象...)

当使用简单的数据类型(例如数字或字符串)时,上述方法中的某些方法可以很好地工作,但是当数组包含其他对象时,这些方法将失败。当我们尝试将任何对象从一个数组传递到另一个数组时,它将作为引用而不是对象传递。

在您的 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.from是一个更具可读性的解决方案。顺便提一下,请注意它的浏览器支持。

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

重要!

这里的大多数答案适用于特定情况

如果您不关心深层 / 嵌套对象和道具的使用( ES6 ):

let clonedArray = [...array]

但是,如果要进行深度克隆,请改用以下方法:

let cloneArray = JSON.parse(JSON.stringify(array))


对于 lodash 用户:

let clonedArray = _.clone(array) 文档

let clonedArray = _.cloneDeep(array) 文档