在Angular中深度复制对象

97

AngularJS 有 angular.copy() 来实现对象和数组的深度拷贝。

Angular 中是否也有类似的方法呢?


5
可能是 How can i use angular.copy in angular 2 的重复问题。 - Günter Zöchbauer
1
可能会有重复,但我想要一个非 polyfill 的解决方案。就像 angular.copy() 一样。 - Ankit Singh
这就是我们所拥有的全部。 - Günter Zöchbauer
所以,没有 angular.copy() 啊。我应该删除这个问题吗? - Ankit Singh
我猜是这样。保留它似乎没有太多价值,因为还有一个非常相似的问题。 - Günter Zöchbauer
12个回答

129

你也可以使用:

JSON.parse(JSON.stringify(Object))

如果它在你的范围内,它将存在于每个Angular组件、指令等中,也存在于每个节点环境中。

除非你有循环引用,否则它应该可以工作,并且将有效地使你的变量引用与原始对象分离。


1
这似乎是一个非常简单而有效的深拷贝!为什么它得分不高?这个答案有问题吗? - TSG
4
如果你正在使用带有函数(而不仅仅是值)的对象文字,则这些函数将被省略,但你可以轻松地组合 Object.assing({}, oldObject, JSON.parse(JSON.stringify(oldObject))) ,这将从你的对象文字重新填充函数属性,并使用JSON创建一个与原始对象没有任何关系的深层副本。 - Gabriel Balsa Cantú
在我看来,最佳答案 - Johannes Wanzek
11
注意,当涉及到日期时,这种方法可能不起作用。 - František Žiačik
1
@FrantišekŽiačik 日期将被格式化为JSON类型,这与接收Rest响应时的类似行为。作为基本行为,它是简单且零依赖的,对于更完整的解决方案,建议将其作为工厂函数来补充,以将JSON日期字符串恢复为日期对象。这是一个“适应需求”的问题,而不是“一刀切”的问题 :) - Gabriel Balsa Cantú
显示剩余2条评论

23

另一个选项是实现您自己的功能:

/**
 * Returns a deep copy of the object
 */
public static deepCopy(oldObj: any) {
    var newObj = oldObj;
    if (oldObj && typeof oldObj === "object") {
        if (oldObj instanceof Date) {
           return new Date(oldObj.getTime());
        }
        newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {};
        for (var i in oldObj) {
            newObj[i] = this.deepCopy(oldObj[i]);
        }
    }
    return newObj;
}

1
我无法使用这种方法深度复制日期。 - A_J
我已经基于如何在JavaScript中克隆Date对象的方法添加了一个Date的克隆。 - jcubic

22

这个问题不是如何在Angular 2中使用angular.copy的副本,因为提问者询问深拷贝对象的方法。链接的答案推荐使用Object.assign(),但它并不会做深拷贝。

实际上,使用Angular2并不限制您使用其他库,例如jQuery$.clone()函数或lodash_.cloneDeep()来进行对象的深拷贝。

大多数常用库都可以通过typings CLI工具获得其类型定义文件,因此即使从TypeScript进行转换,您也可以无缝地使用任何您想要的内容。

另请参阅:在JavaScript中深克隆对象的最有效方法是什么?


Object.assign()在目标对象中存在嵌套对象时不会进行深拷贝,因此作为一种简单的解决方案,您可以遍历目标对象并对其每个属性执行Object.assign()!当我不想依赖任何第三方库时,这对我很有用。 - Dany Wehbe

21

您可以使用lodash的cloneDeep方法在Angular中进行对象的深度复制:

通过yarn add lodashnpm install lodash安装lodash。

在您的组件中,导入cloneDeep并使用它:

import * as cloneDeep from 'lodash/cloneDeep';
...
clonedObject = cloneDeep(originalObject);

仅增加了18kb到您的构建中,这些好处是非常值得的。

如果您需要更深入地了解为什么要使用lodash的cloneDeep方法,我也在这里写了一篇文章


1
使用 Lodash 进行值复制没有起作用,你知道为什么吗?如果我改变一个值,它们仍然会同时更改,因此它们引用的是同一份数据。 - Vladimir Despotovic

7
Create helper class with name deepCopy.ts

/*
* DeepCopy class helps to copy an Original Array or an Object without impacting on original data
*/

export class DeepCopy {

  static copy(data: any) {
    let node;
    if (Array.isArray(data)) {
      node = data.length > 0 ? data.slice(0) : [];
      node.forEach((e, i) => {
        if (
          (typeof e === 'object' && e !== {}) ||
          (Array.isArray(e) && e.length > 0)
        ) {
          node[i] = DeepCopy.copy(e);
        }
      });
    } else if (data && typeof data === 'object') {
      node = data instanceof Date ? data : Object.assign({}, data);
      Object.keys(node).forEach((key) => {
        if (
          (typeof node[key] === 'object' && node[key] !== {}) ||
          (Array.isArray(node[key]) && node[key].length > 0)
        ) {
          node[key] = DeepCopy.copy(node[key]);
        }
      });
    } else {
      node = data;
    }
    return node;
  }
}

无论何时需要,请导入deepCopy文件并按如下代码使用DeepCopy.copy(arg);,这里的arg可以是您想要的对象或数组。


1
这实际上是最好的答案。它可以有效地深度复制数据,而不需要导入外部库。我不想为了使用一个函数而引入整个库(即使它只有18KB)。 - Frozenfrank
最佳答案就是这个..!太棒了!! - K L P
5
我不会盲目地复制这个片段。它可以工作,但有缺陷。在数组和对象断言中有重复的代码。在那个重复的代码中,e!=={} 总是为真,而且由于 typeof [] === 'object',OR-pipes 后面的部分永远不会按预期工作。这也会简单地复制遇到的任何日期。那个三元运算符的第一部分最好看起来像 ? new Date(data) : - Sjeiti

5

对于KrishnamrajuK的答案进行一些修改

export class DeepCopy {
  static copy(data: any, objMap?: WeakMap<any, any>) {
    if (!objMap) {
      // Map for handle recursive objects
      objMap = new WeakMap();
    }

    // recursion wrapper
    const deeper = value => {
      if (value && typeof value === 'object') {
        return DeepCopy.copy(value, objMap);
      }
      return value;
    };

    // Array value
    if (Array.isArray(data)) return data.map(deeper);

    // Object value
    if (data && typeof data === 'object') {
      // Same object seen earlier
      if (objMap.has(data)) return objMap.get(data);
      // Date object
      if (data instanceof Date) {
        const result = new Date(data.valueOf());
        objMap.set(data, result);
        return result;
      }
      // Use original prototype
      const node = Object.create(Object.getPrototypeOf(data));
      // Save object to map before recursion
      objMap.set(data, node);
      for (const [key, value] of Object.entries(data)) {
        node[key] = deeper(value);
      }
      return node;
    }
    // Scalar value
    return data;
  }
}


2
这个答案涉及到递归对象,使得它成为最佳答案! - EQuadrado

1
如果源是对象数组,可以使用map方法:
let cloned = source.map(x => Object.assign({}, x));

或者

let cloned = source.map((x) => {
                return { ...x };
             });

1

这里的一些答案依赖于lodash,在导入angular 10+时可能会出现警告消息,例如:

WARNING in xxxxx.ts depends on lodash/cloneDeep. CommonJS or AMD dependencies can cause optimization bailouts.

其他答案使用JSON解析或尝试自己实现。为了不重复造轮子,我们使用clone:一个具有有限依赖关系的轻量级克隆实用程序。
要使用clone,您只需要安装这两个软件包:
npm install clone
npm install --save-dev @types/clone

创建一个服务(服务不是必需的,但我更喜欢这种方法),该服务使用克隆API:

import { Injectable } from '@angular/core';
import * as clone from 'clone';

@Injectable()
export class ObjectCloneService {
    public cloneObject<T>(value: T): T {
        return clone<T>(value);
    }
}

记得将服务添加到你的模块中。


0

如果您想使用cloneDeep而不需要任何“CommonJS或AMD依赖项可能会导致优化失败”,您可以像这样使用它:

npm i lodash-es --save

然后在任何组件中都可以简单地使用:

import { cloneDeep } from 'lodash-es';
// ...
let a = cloneDeep(b);

0
我面临深拷贝的问题。angular.copy({}, factory) 和 angular.extend({}, factory) 可以很好地帮助数组或散列对象进行复制,但是当复制一个类对象时,有时可能会出现连接依赖方面的问题。我解决了这个问题:
 copyFactory = (() ->
    resource = ->
      resource.__super__.constructor.apply this, arguments
      return
    this.extendTo resource
    resource
  ).call(factory)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接