【问题标题】:Cloning an array in Javascript/Typescript在 Javascript/Typescript 中克隆数组
【发布时间】:2017-12-02 04:41:46
【问题描述】:

我有两个对象的数组:

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

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

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

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

这是如何发生的以及如何预防?

【问题讨论】:

  • 看起来你做了一个数组的浅拷贝。听起来您正在修改他们持有的对象并查看更改。您需要制作深拷贝或想出一种不同的方式来表示您的数据。
  • 它们指向同一个引用。如果你使用 lodash 或类似的库返回一个新数组,你就不会有这个问题。
  • slice() 我猜会从另一个数组创建新对象...
  • 第二个数组正在被修改,因为您只是在引用原始数组,而不是创建一个新数组。如果您使用类型脚本和 ES6,您可以创建这样的副本 this.backupData = [...this.genericItems] 这将创建数组的副本。希望有帮助!
  • @MolikMiah 我是说slice 接受一个数组并将每个引用复制到一个新数组中。所以老数组和新数组其实是不一样的,但是里面的对象是完全一样的。所以应该和[...array]一样

标签: arrays angular typescript


【解决方案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

【讨论】:

    【解决方案2】:

    看起来您可能在复制数组的位置上犯了一个错误。请看下面我的解释,并对代码稍作修改,这应该有助于您将数据重置为之前的状态。

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

    • 您正在请求获取通用项目
    • 获取数据后,将结果设置为 this.genericItems
    • 紧接着您将 backupData 设置为结果

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

    这样会更好吗:

    • 你做数据请求
    • 制作 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;
        });
      }
    

    【讨论】:

      【解决方案3】:

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

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

      所以虽然backupDatagenericItems 是不同的数组,但它们包含完全相同的对象引用。

      您可以引入一个库来为您进行深度复制(正如@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());
      

      【讨论】:

        【解决方案4】:

        看起来你想要的是对象的深拷贝。为什么不使用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() 做同样的事情。它创建一个新数组,但从旧数组复制对象引用。
        • 另外,我认为应该是Object.assign([], result)。否则,我认为您将失去 length 属性(可能还有其他一些东西)。
        【解决方案5】:

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

        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 cloneDeep API(这可以作为一个单独的模块安装),它可以帮助您复制对象内部的对象,完全解除对原始对象的引用。

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

        独立包https://www.npmjs.com/package/lodash.clonedeep

        【讨论】:

          【解决方案6】:

          我对 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) 还是不行。

          【讨论】:

            【解决方案7】:

            克隆一个对象:

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

            【讨论】:

            • 如果你的数组是一个对象数组(不是原始类型),那么你的浅拷贝需要更深一层。对我来说,解决方案是遍历数组并克隆对象。 IE。 const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }]; const myClonedArray = []; myArray.map(val =&gt; myClonedArray.push(Object.assign({}, val)));。如其他答案中所述,正确深拷贝的替代解决方案是 JSON 序列化。
            • @mumblesNZ,如果您真的在谈论深拷贝,那么两个级别也不够。您必须使用 Lodash 的 _.cloneDeep(obj) 之类的东西。 JSON 序列化可以像你说的那样工作,但这是一种非常迂回的方式。
            • 此答案的选项 2 部分适用于包含原语的对象。如果数组元素包含一个数组或对象的值,则深拷贝会有所帮助,正如@user2683747 提到的那样。
            • 关于深拷贝和浅拷贝的有用参考。techmeet360.com/blog/playing-with-javascript-object-clone
            【解决方案8】:

            克隆数组最简单的方法是

            backUpData = genericItems.concat();
            

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

            【讨论】:

            • 这不会为backUpData 创建新内存。 backUpData 仍然持有 genericItems 的引用。
            【解决方案9】:

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

            在您的情况下,这应该可以:

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

            【讨论】:

            • 在不使用任何新库的情况下深度克隆对象数组的最佳解决方案
            【解决方案10】:

            试试这个

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

            并将空数组传递给目标

            如果这种方式对我有用的复杂对象

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

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

            【讨论】:

              【解决方案11】:

              在 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}}]
              

              【讨论】:

              • 啊!一分钱掉了!是时候停止使用扩展运算符来克隆对象数组了
              • const myClonedArray = [...myArray] 正在为 [{a:1}, {b:2}] 工作。
              • 不,它没有。尝试修改您的克隆项目,它也会修改原始项目。你所拥有的是通过引用而不是价值复制。
              • 对于深度克隆也可以使用“Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);”而不是使用外部库。
              • 这是适用于数组数组的答案
              【解决方案12】:

              你可以使用地图功能

               toArray= fromArray.map(x => x);
              

              【讨论】:

                【解决方案13】:

                如果您的数组中的项目不是原始的,您可以使用扩展运算符来做到这一点。

                this.plansCopy = this.plans.map(obj => ({...obj}));
                

                完整答案:https://stackoverflow.com/a/47776875/5775048

                【讨论】:

                  【解决方案14】:

                  试试下面的代码:

                  this.cloneArray= [...this.OriginalArray]
                  

                  【讨论】:

                    【解决方案15】:

                    以非常强大的方式克隆一个对象/数组(不带引用)

                    您可以使用@angular-devkit 获取您的object / array 的深层副本。

                    import { deepCopy } from '@angular-devkit/core/src/utils/object';
                    
                    export class AppComponent {
                    
                      object = { .. some object data .. }
                      array = [ .. some list data .. ]
                    
                      constructor() {
                         const newObject = deepCopy(this.object);
                         const newArray = deepCopy(this.array);
                      }
                    }
                    

                    【讨论】:

                    • 太好了,这很有效,为不喜欢或不依赖 loaddash 的人提供了本机解决方案
                    • 做到了,非常感谢。我很惊讶为什么扩展运算符或切片等其他方法不起作用。将目标数组分配给源数组,总是改变源数组,真是一场灾难。
                    猜你喜欢
                    • 2021-01-07
                    • 2015-02-12
                    • 2011-01-18
                    • 2018-05-26
                    • 2010-10-10
                    • 2013-09-10
                    相关资源
                    最近更新 更多