关于javascript:按值复制数组

Copy array by value

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

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

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


使用此:

1
var newArray = oldArray.slice();

基本上,slice()操作克隆数组并返回对新数组的引用。还要注意:

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

字符串和数字等基元是不可变的,因此不可能更改字符串或数字。


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

三种元素

元素可以是:文本值、文本结构或原型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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
2
3
4
5
6
7
8
// 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 deep copy techniques by element types

  • 文本值数组(type1)[...myArray]myArray.splice(0)myArray.slice()myArray.concat()技术只能用于深度复制具有文字值(布尔值、数字和字符串)的数组;其中,扩展运算符[...myArray]的性能最好(https://measurethat.net/bencheps/show/4281/0/扩展数组性能与切片拼接concat)。

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

  • 所有数组(类型1、类型2、类型3)jquery $.extend(myArray)技术可用于深度复制所有数组类型。像下划线和lo-dash这样的库提供类似于jquery $.extend()的深度复制功能,但性能较低。更令人惊讶的是,$.extend()的性能比JSON.parse(JSON.stringify(myArray))技术(http://jspef.com/js-deep-copy/15)更高。对于那些不愿使用第三方库(如jquery)的开发人员,您可以使用下面的定制函数;它的性能高于$.extend,并且可以深度复制所有数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}

所以要回答这个问题…

问题

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

I realized that arr2 refers to the same array as arr1, rather than a
new, independent array. How can I copy the array to get two
independent arrays?

回答

由于arr1是一个文本值数组(布尔值、数字或字符串),因此可以使用上面讨论的任何深度复制技术,其中扩展运算符...的性能最高。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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


可以使用array spreads ...复制数组。

const itemsCopy = [...items];

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

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

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

有关价差的更多信息


不需要jquery…工作实例

1
var arr2 = arr1.slice()

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

重要的是要注意,对于基元类型(字符串、数字等),它将按预期工作,并解释引用类型的预期行为…

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


替代slice的是concat,它可以用两种方式使用。由于预期行为非常清楚,因此第一种可能更易于阅读:

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

第二种方法是:

1
var array2 = array1.concat();

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

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

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

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

或者,对于第二种方法:

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

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


这就是我在尝试了很多方法之后所做的:

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

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

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


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

在javascript文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
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;
};

简单使用

1
2
var arr1 = ['val_1','val_2','val_3'];
var arr2 = arr1.clone()

它会起作用的。


从ES2015

1
var arr2 = [...arr1];

我个人认为array.from是一个更易读的解决方案。顺便说一句,小心它的浏览器支持。

1
2
3
4
5
6
7
8
//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);


是重要的。 P / < >

现在的工程为例具体回答的睾丸。 P / < >

如果你不在乎深/一个嵌套的物体和props使用(es6): P / < >

let clonedArray = [...array] P / < >

但是,如果你想做深克隆用这个代替: P / < >

let cloneArray = JSON.parse(JSON.stringify(array)) P / < >

为lodash用户: P / < >

let clonedArray = _.clone(array)文件 P / < >

和 P / < >

let clonedArray = _.cloneDeep(array)文件 P / < >


如果您处于ecmascript 6的环境中,使用spread操作符可以这样做:

1
2
3
4
5
6
var arr1 = ['a','b','c'];
var arr2 = [...arr1]; //copy arr1
arr2.push('d');

console.log(arr1)
console.log(arr2)
1
<script src="http://www.wzvang.com/snippet/ignore_this_file.js">


添加到array.slice()的解决方案中;请注意,如果有多维数组,则子数组将被引用复制。您可以分别循环和切片()每个子数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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]);

同样的事情也会发生在对象数组中,它们将被引用复制,您必须手动复制它们。


我们知道在javascript arrays和物体是由参考的方式,但什么是我们可以做的没有改变原有的拷贝的阵列的阵列以后的一个吗? P / < >

这里是一些的方式做它。 P / < >

假设我们有一个这样的阵列在你的代码: P / < >

1
var arr = [1, 2, 3, 4, 5];

1)looping通过在阵列的功能和回报的新的阵列,像这样: P / < >

1
2
3
4
5
6
7
8
 function newArr(arr) {
      var i=0, res = [];
      while(i<arr.length){
       res.push(arr[i]);
        i++;
       }
   return res;
 }

2)使用的切片的切片的方法,也为slicing部分的阵列,它将日常的一些部分的阵列没有touching之原,在的切片,如果不要specify开始和结束的阵列,它将整个阵列的切片和basically做出完整的拷贝的阵列,所以我们可以易说: P / < >

1
var arr2 = arr.slice(); // make a copy of the original array

3)也接触的方法,这为合并两个阵列,但我们不能只是一个specify arrays,然后让这basically拷贝的* *的值在纽约contacted阵列: P / < >

1
var arr2 = arr.concat();

4)也stringify和parse法,是不推荐的,但可以成为一个简单的方式来复制和阵列的物体: P / < >

1
var arr2 = JSON.parse(JSON.stringify(arr));

5 array.from)的方法,这是不widely supported,使用之前检查一下支持在不同的browsers: P / < >

1
const arr2 = Array.from(arr);

6 ecma6)的方式,也不全supported,但babeljs可以帮助你,如果你想transpile: P / < >

1
const arr2 = [...arr];

复制多维数组/对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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提供此功能。

来源:这里


在我的特殊情况下,我需要确保阵列保持完整,因此这对我有效:

1
2
3
4
5
6
// 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]);
}


丹,不需要使用花哨的tricks。所有你需要做的是让拷贝的arr1做这。 P / < >

1
var arr2 = new Array(arr1);

P / < >

现在arr1arr2是两个不同的变量的描述stored阵列在separate栈。 看看这个jsfiddle C了。 P / < >


当我们要复制一个阵列使用的分配算子(=),它没有被创建的拷贝它merely copies * / 指针参考的阵列。例如: P / < >

1
2
3
4
5
6
7
8
9
const oldArr = [1,2,3];

const newArr = oldArr;  // now oldArr points to the same place in memory

console.log(oldArr === newArr);  // Points to the same place in memory thus is true

const copy = [1,2,3];

console.log(copy === newArr);  // Doesn't point to the same place in memory and thus is false

P / < >

当我们often transform数据我们要保持我们的初始datastructure(例如阵列intact)。我们这样做,由制作的精确拷贝的阵列,所以我们可以在这一transformed的初始intact一停止,风格。 P / < > 的方式copying of an阵列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const oldArr = [1,2,3];

// Uses the spread operator to spread out old values into the new array literal
const newArr1 = [...oldArr];

// Slice with no arguments returns the newly copied Array
const newArr2 = oldArr.slice();

// Map applies the callback to every element in the array and returns a new array
const newArr3 = oldArr.map((el) => el);

// Concat is used to merge arrays and returns a new array. Concat with no args copies an array
const newArr4 = oldArr.concat();

// Object.assign can be used to transfer all the properties into a new array literal
const newArr5 = Object.assign([], oldArr);

// Creating via the Array constructor using the new keyword
const newArr6 = new Array(...oldArr);

// For loop
function clone(base) {
    const newArray = [];
    for(let i= 0; i < base.length; i++) {
        newArray[i] = base[i];
    }
    return newArray;
}

const newArr7 = clone(oldArr);

console.log(newArr1, newArr2, newArr3, newArr4, newArr5, newArr6, newArr7);

P / < > 小心点,当arrays或物体是一个嵌套的。:

当arrays是一个嵌套的值是copied by参考。这里是一个例子的技术,这可能导致到的问题: P / < >

1
2
3
4
5
6
7
let arr1 = [1,2,[1,2,3]]

let arr2 = [...arr1];

arr2[2][0] = 5;  // we change arr2

console.log(arr1);  // arr1 is also changed because the array inside arr1 was copied by reference

P / < >

所以不要使用这些方法,当有物体或在你的arrays阵列你想要拷贝的。公元前使用这些方法在arrays of primitives只。 P / < >

如果你想做的deepclone javascript阵列使用JSON.parse在conjunction与JSON.stringify,像这样: P / < >

1
2
3
4
5
6
7
let arr1 = [1,2,[1,2,3]]

let arr2 = JSON.parse(JSON.stringify(arr1)) ;

arr2[2][0] = 5;

console.log(arr1);  // now I'm not modified because I'm a deep clone

P / < > 性能:copying

所以,我们做一个选择为最佳的性能。它变了,现在verbose方法,for腹部环的highest性能。使用"for环为真的CPU的深入copying(大/多arrays)。 P / < >

之后,.slice()方法也有相当不错的表现,也是偷看verbose和easier农药programmer到implement。我建议到使用.slice()每天为你的copying of arrays,难道CPU很深入。也避免使用"JSON.parse(JSON.stringify(arr))(* overhead)如果没有深克隆咨询需要和性能是一个问题。 P / < >

源代码的性能试验 P / < >


你也可以使用es6传播算子拷贝到阵列 P / < >

1
2
var arr=[2,3,4,5];
var copyArr=[...arr];

如果要创建对象或数组的新副本,必须显式复制对象或数组元素的属性,例如:

1
2
3
4
5
6
var arr1 = ['a','b','c'];
var arr2 = [];

for (var i=0; i < arr1.length; i++) {
   arr2[i] = arr1[i];
}

您可以在Google上搜索关于不可变原语值和可变对象引用的更多信息。


快速examples: P / < >

  • 如果元素在阵列是primitive类型(字符串,数字,等。)
  • 1
    2
    3
    4
    5
    6
    7
    var arr1 = ['a','b','c'];
    // arr1 and arr2 are independent and primitive elements are stored in
    // different places in the memory
    var arr2 = arr1.slice();
    arr2.push('d');
    console.log(arr1); // [ 'a', 'b', 'c' ]
    console.log(arr2); // [ 'a', 'b', 'c', 'd' ]

    P / < >

  • 如果元素在阵列是literals对象,另一个阵列({ } [ ])
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];
    // arr1 and arr2 are independent and reference's/addresses are stored in different
    // places in the memory. But those reference's/addresses points to some common place
    // in the memory.
    var arr2 = arr1.slice();
    arr2.pop();      // OK - don't affect arr1 bcos only the address in the arr2 is
                     // deleted not the data pointed by that address
    arr2[0].x = 'z'; // not OK - affect arr1 bcos changes made in the common area
                     // pointed by the addresses in both arr1 and arr2
    arr2[1][0] = 9;  // not OK - same above reason

    console.log(arr1); // [ { x: 'z', y: 'b' }, [ 9, 2 ], [ 3, 4 ] ]
    console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]

    P / < >

  • 解决方案为:深拷贝的= = 2元元
  • 1
    2
    3
    4
    5
    6
    7
    8
    var arr1 = [{ x: 'a', y: 'b'}, [1, 2], [3, 4]];
    arr2 = JSON.parse(JSON.stringify(arr1));
    arr2.pop();   // OK - don't affect arr1
    arr2[0].x = 'z';  // OK - don't affect arr1
    arr2[1][0] = 9;   // OK - don't affect arr1

    console.log(arr1); // [ { x: 'a', y: 'b' }, [ 1, 2 ], [ 3, 4 ] ]
    console.log(arr2); // [ { x: 'z', y: 'b' }, [ 9, 2 ] ]

    P / < >


    使用jquery深度复制可以进行如下操作:

    1
    var arr2 = $.extend(true, [], arr1);

    这里是一些更多的方式来拷贝: P / < >

    1
    2
    3
    4
    5
    6
    const array = [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 );

    P / < >


    如果数组包含基元数据类型的元素(如int、char或string等),则可以使用这些方法中的一种,该方法返回原始数组的副本(如.slice()或.map()或spread运算符,这要归功于es6)。

    1
    new_array = old_array.slice()

    1
    new_array = old_array.map((elem) => elem)

    1
    const new_array = new Array(...old_array);

    但是,如果数组包含对象(或数组)或更多嵌套对象等复杂元素,则必须确保将所有元素从顶层复制到最后一层,否则将使用内部对象的引用,这意味着更改新数组中对象元素的值仍会影响旧数组γ阵列。您可以在每个级别将此复制方法称为进行深度复制旧阵列的。

    对于深度复制,您可以根据数据类型在每个级别对原始数据类型使用上面提到的方法,或者可以使用这种昂贵的方法(下面提到)进行深度复制,而不需要做很多工作。

    1
    var new_array = JSON.parse(JSON.stringify(old_array));

    根据您的需求,有很多其他方法可以使用。我只提到了其中的一些内容,用于大致了解当我们尝试按值将数组复制到另一个数组时会发生什么。


    有新推出的Array.from,但不幸的是,截至本文撰写之时,它仅在最新的火狐版本(32及更高版本)上受支持。它可以简单地如下使用:

    1
    2
    var arr1 = [1, 2, 3];
    console.log(Array.from(arr1)); // Logs: [1, 2, 3]

    参考文献:这里

    Array.prototype.map可用于标识功能:

    1
    2
    3
    4
    5
    6
    7
    function identity(param)
    {
        return param;
    }

    var arr1 = [1, 2, 3],
        clone = arr1.map(identity);

    参考文献:这里


    你可以用es6与传播opeartor,其simpler。 P / < >

    1
    arr2 = [...arr1];

    有limitations ..check价差文档语法mozilla @ P / < >


    你可以这样做,在以下的方式: arr2 = arr1.map(x => Object.assign({}, x)); P / < >


    1
    let a = [1,2,3];

    现在,您可以执行以下任一操作来创建数组的副本。

    1
    let b = Array.from(a);

    1
    let b = [...a];

    1
    let b = new Array(...a);

    1
    let b = a.slice();

    1
    let b = a.map(e => e);

    现在,如果我换了A,

    1
    a.push(5);

    那么,a是[1,2,3,5]但是b仍然是[1,2,3],因为它有不同的引用。

    但我认为,在上述所有方法中,array.from更好,主要是为了复制一个数组。


    这里有一个变种:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var arr1=['a', 'b', 'c'];
    var arr2=eval(arr1.toSource());
    arr2.push('d');
    console.log('arr1: '+arr1+'
    arr2: '
    +arr2);
    /*
     *  arr1: a,b,c
     *  arr2: a,b,c,d
     */


    下面是如何对可变深度的基元数组进行此操作:

    1
    2
    3
    4
    5
    6
    7
    8
    // If a is array:
    //    then call cpArr(a) for each e;
    //    else return a

    const cpArr = a => Array.isArray(a) && a.map(e => cpArr(e)) || a;

    let src = [[1,2,3], [4, ["five","six", 7], true], 8, 9, false];
    let dst = cpArr(src);

    https://jsbin.com/xemazog/edit?控制台


    基本值总是通过其值传递(复制)。复合值通过引用传递。

    那么我们如何复制这个ARR?

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

    在ES6中复制数组

    1
    let arrCopy = [...arr];

    在ES5中复制n数组

    1
    2
    let arrCopy = arr.slice();
    let arrCopy = [].concat(arr);

    为什么'let arrcopy=arr'不按值传递?

    在复合值(如对象/数组)上将一个变量传递给另一个变量的行为不同。在copand值上使用asign运算符,我们将引用传递给对象。这就是删除/添加arr元素时两个数组的值都在变化的原因。

    Exceptios:

    1
    arrCopy[1] = 'adding new value this way will unreference';

    为变量指定新值时,将更改引用本身,而不会影响原始对象/数组。

    多读


    我个人更喜欢这种方式json.parse(json.stringify(originalObject));


    为es6阵列包含的物体 P / < >

    1
    2
    3
    cloneArray(arr) {
        return arr.map(x => ({ ...x }));
    }

    只是写作:

    1
    arr2 = arr1.concat();

    您正在使用第一个数组的副本生成新的数组。请注意,这是将元素推入数组的一种功能性方法。

    如果您的代码基于ES6,则也可以使用排列运算符:

    1
    arr2 = [...arr1];