用Javascript / Typescript克隆数组


76

我有两个对象的数组:

genericItems: Item[] = [];
backupData: Item[] = [];

我正在用genericItems数据填充HTML表。该表是可修改的。有一个重置按钮可以撤消使用所做的所有更改backUpData。此数组由服务填充:

getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
  result => {
     this.genericItems = result;
  });
     this.backupData = this.genericItems.slice();
  }

我的想法是,用户更改将反映在第一个阵列中,而第二个阵列可用作重置操作的备份。我在这里面临的问题是用户修改表时(genericItems[])第二个数组backupData也被修改了。

这是怎么回事,如何预防呢?


看起来您对数组进行了浅拷贝。听起来您正在修改它们持有的对象并看到更改。您需要进行深拷贝或提出其他表示数据的方法。
杰夫·梅卡多

他们指向相同的参考。如果使用lodash或类似的库返回新数组,则不会有此问题。
拉丁勇士

slice()我会从另一个数组中创建新对象...
Arun

第二个数组正在修改,因为您没有创建新的数组,而只是引用原始数组。如果使用类型脚本和ES6,则可以创建类似this.backupData = [... this.genericItems]的副本,这将创建数组的副本。希望有帮助!
Molik Miah

2
@MolikMiah我是说这slice需要一个数组并将每个引用复制到一个新数组中。因此,旧数组和新数组实际上是不同的,但是其中的对象完全相同。所以应该和做的一样[...array]
Frank Modica

Answers:


158

克隆对象:

const myClonedObject = Object.assign({}, myObject);

克隆数组:

  • 如果具有原始类型数组,则为选项1

const myClonedArray = Object.assign([], myArray);

  • 选项2-如果您具有对象数组:
const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }];
const myClonedArray = [];
myArray.forEach(val => myClonedArray.push(Object.assign({}, val)));

13
如果您的数组是对象数组(不是基本类型),那么您需要对浅表副本进行更深一层的研究。对我来说,解决方案是遍历数组并克隆对象。即const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }]; const myClonedArray = []; myArray.map(val => myClonedArray.push(Object.assign({}, val)));。正确的深层副本的替代解决方案是针对JSON序列化,如其他答案中所述。
mumblesNZ

@mumblesNZ,如果您真的在谈论深拷贝,那么两个级别也不足够。您必须使用类似Lodash的东西_.cloneDeep(obj)。就像您说的那样,JSON序列化将起作用,但这是一种非常round回的方式。
user2683747 '19

64

javascript中的克隆数组和对象具有不同的语法。迟早每个人都会通过艰辛的方式学习差异并最终来到这里。

TypescriptES6中,您可以将散布运算符用于数组和对象:

const myClonedArray  = [...myArray];  // This is ok for [1,2,'test','bla']
                                      // But wont work for [{a:1}, {b:2}]. 
                                      // A bug will occur when you 
                                      // modify the clone and you expect the 
                                      // original not to be modified.
                                      // The solution is to do a deep copy
                                      // when you are cloning an array of objects.

要对对象进行深层复制,您需要一个外部库:

import {cloneDeep} from 'lodash';
const myClonedArray = cloneDeep(myArray);     // This works for [{a:1}, {b:2}]

散布运算符也适用于对象,但只会进行浅表复制(子级的第一层)

const myShallowClonedObject = {...myObject};   // Will do a shallow copy
                                               // and cause you an un expected bug.

要对对象进行深层复制,您需要一个外部库:

 import {cloneDeep} from 'lodash';
 const deeplyClonedObject = cloneDeep(myObject);   // This works for [{a:{b:2}}]

1
啊! 一分钱掉下来!是时候停止使用传播运算符来克隆对象数组了
sidonaldson

const myClonedArray = [... myArray]适用于[{a:1},{b:2}]。
Suyash Gupta,

2
不,不是的。尝试修改克隆的项目,它也会修改原始项目。您所拥有的只是参考复制,而不是价值。
戴维·德汉

对于深克隆,也可以使用“ Object.assign(Object.create(Object.getPrototypeOf(obj)),obj);” 而不是使用外部库。
CoderApprentice

18

使用地图或其他类似解决方案无助于深深克隆对象数组。在不添加新库的情况下执行此操作的更简单方法是使用JSON.stringfy,然后使用JSON.parse。

在你的情况下,这应该工作:

this.backupData = JSON.parse(JSON.stringify(genericItems));


3

代码中的以下行创建一个新数组,将所有对象引用从中复制genericItems到该新数组中,并将其分配给backupData

this.backupData = this.genericItems.slice();

因此,虽然backupDataandgenericItems是不同的数组,但它们包含相同的确切对象引用。

您可以引入一个库来为您做深度复制(如@LatinWarrior所述)。

但是如果Item不太复杂,也许您可​​以向其中添加一个clone方法来自己深克隆对象:

class Item {
  somePrimitiveType: string;
  someRefType: any = { someProperty: 0 };

  clone(): Item {
    let clone = new Item();

    // Assignment will copy primitive types

    clone.somePrimitiveType = this.somePrimitiveType;

    // Explicitly deep copy the reference types

    clone.someRefType = {
      someProperty: this.someRefType.someProperty
    };

    return clone;
  }
}

然后调用clone()每个项目:

this.backupData = this.genericItems.map(item => item.clone());

3

我对primeNg DataTable有相同的问题。经过尝试和哭泣,我使用此代码解决了该问题。

private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
    const result: SelectItem[] = [];
    if (!arr) {
      return result;
    }
    const arrayLength = arr.length;
    for (let i = 0; i <= arrayLength; i++) {
      const item = arr[i];
      if (item) {
        result.push({ label: item.label, value: item.value });
      }
    }
    return result;
  }

用于初始化备用值

backupData = this.deepArrayCopy(genericItems);

重置更改

genericItems = this.deepArrayCopy(backupData);

最妙的方法是通过使用{}而不是调用构造函数来重新创建项目。我试过了new SelectItem(item.label, item.value)不起作用。



1

试试这个:

[https://lodash.com/docs/4.17.4#clone][1]

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

1

看来您在哪里做阵列副本可能犯了一个错误。请看下面我的解释以及对代码的略微修改,这些代码可以帮助您将数据重置为以前的状态。

在您的示例中,我可以看到发生了以下情况:

  • 您正在请求获取通用商品
  • 获取数据后,将结果设置为this.genericItems
  • 之后,您直接将backupData设置为结果

我认为您不希望第3点按此顺序发生是对的吗?

会更好吗:

  • 您执行数据请求
  • 备份this.genericItems中最新的内容
  • 然后根据您的请求设置genericItems

试试这个:

getGenericItems(selected: Item) {
  this.itemService.getGenericItems(selected).subscribe(
    result => {
       // make a backup before you change the genericItems
       this.backupData = this.genericItems.slice();

       // now update genericItems with the results from your request
       this.genericItems = result;
    });
  }

1

下面的代码可能会帮助您复制第一级对象

let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))

因此在以下情况下,值保持不变

copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)

在这种情况下失败

let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(

最终建议:

我会说去lodash cloneDeepAPI,它可以帮助您复制完全脱离原始对象的对象内的对象。可以将其安装为单独的模块。

参考文档: https : //github.com/lodash/lodash

单独的软件包https : //www.npmjs.com/package/lodash.clonedeep


1

克隆数组最简单的方法是

backUpData = genericItems.concat();

这将为数组索引创建一个新的内存


这不会为创建新的内存backUpDatabackUpData仍然拥有参考genericItems
Suyash Gupta,


0

看起来您想要的是对象的Deep Copy。为什么不使用Object.assign()?不需要任何库,它只有一种格式:)

getGenericItems(selected: Item) {
    this.itemService.getGenericItems(selected).subscribe(
        result => {
            this.genericItems = result;
            this.backupDate = Object.assign({}, result); 
            //this.backupdate WILL NOT share the same memory locations as this.genericItems
            //modifying this.genericItems WILL NOT modify this.backupdate
        });
}

有关更多信息Object.assign()https : //developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


这与做相同的事情slice()。它创建一个新数组,但是从旧数组中复制对象引用。
Frank Modica

另外,我认为应该是Object.assign([], result)。否则,我认为您会失去length财产(也许还有其他一些东西)。
Frank Modica

-1

试试这个

const returnedTarget = Object.assign(target, source);

并将空数组传递给目标

万一复杂的对象对我有用

$.extend(true, [], originalArray) 如果是数组

$.extend(true, {}, originalObject) 在对象的情况下

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.