Angular中angular.copy的替代方法是什么


135

如何在Angular中复制对象并丢失其引用?

使用AngularJS,我可以使用angular.copy(object),但是在Angular中使用它会出现一些错误。

例外:ReferenceError:angular未定义


检查此解决方案可能会有所帮助:链接
Nourdine Alouane

在许多情况下,您可能想使用.copy()但实际上并不需要它。在我所看到的各种AngJS1项目中,这是一个过大的杀伤力,如果要手动编写相关子结构的副本,就可以获得更清晰的代码。也许这是Angular团队不实施此决定的一部分。
phil294

顺便说一下,相关(也未得到答案):stackoverflow.com/questions/41124528/…–
phil294

Answers:


180

假设您正在使用ES6,则可以使用var copy = Object.assign({}, original)。在现代浏览器中工作;如果您需要支持旧版浏览器,请查看此polyfill

更新:

使用TypeScript 2.1+,可以使用ES6速记对象传播符号:

const copy = { ...original }

75
请注意,这angular.copy()会创建与相反的深层副本Object.assign()。如果您想进行深拷贝,请使用lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg

在Webstorm中我得到了Unresolved function or method assign(); IDE详细信息:Webstorm 2016.2。我该如何解决?
mihai

2
@meorfi转到File -> Settings -> Languages & Frameworks -> Javascript并设置Javascript language versionECMAScript 6.0
Siri0S

@bertrandg _.clone(值)大于angular.copy()不同,它不会产生新的实例,从而_.cloneDeep(价值),它还是创建一个参考stackoverflow.com/questions/26411754/...
Zealitude

5
此外,如果要复制数组,请使用:const copy = [ ...original ]
daleyjem

43

直到我们有更好的解决方案,您可以使用以下方法:

duplicateObject = <YourObjType> JSON.parse(JSON.stringify(originalObject));

编辑:澄清

请注意:以上解决方案仅是在Angular 2处于积极开发阶段时提供的一种快速修复衬套。我的希望是我们最终可以得到相当于angular.copy()。因此,我不想编写或导入深克隆库。

此方法还具有解析日期属性的问题(它将成为字符串)。

请不要在生产应用程序中使用此方法。仅在您的实验项目中使用它-您正在学习Angular 2的项目。


11
这会破坏您的约会日期,并且慢如地狱。
LanderV '16

5
只要您正在做的事情很简单
Ian Belcher

1
这太可怕了,永远不要使用
Murhaf Sousli

1
@MurhafSousli请尝试理解此答案的上下文。这是在开发Angular 2时提供的,希望是我们最终将获得等效的angular.copy()函数。为了缩短等待时间,我将这个解决方案作为临时选项,直到我们有了更好的解决方案为止。这是具有深度克隆的单线。我同意,这太可怕了。但是考虑到当时的实验情况,还算不错。
玛尼

1
@LazarLjubenović当然是在2018年,我今天完全同意你的看法,但在2016年webpack并没有摇摇欲坠,因此在大多数情况下,您将导入整个库。
伊恩·贝尔彻

22

深复制内部具有嵌套对象的对象的替代方法是使用lodash的cloneDeep方法。

对于Angular,您可以这样操作:

使用yarn add lodash或安装lodash npm install lodash

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

import { cloneDeep } from "lodash";
...
clonedObject = cloneDeep(originalObject);

它仅增加了18kb,值得物有所值。

如果您需要更多了解为何使用lodash的cloneDeep,我也在这里写了一篇文章


2
“仅18kb”添加到输出中才能够深度复制对象?JavaScript是一团糟。
Endrju

阅读您引用的文章后,我了解了该cloneDeep方法实例化一个新对象。如果我们已经有目标对象,我们应该仍然使用它吗?
斯蒂芬

17

对于浅表复制,您可以使用Object.assign这是ES6的功能

let x = { name: 'Marek', age: 20 };
let y = Object.assign({}, x);
x === y; //false

请勿将其用于深度克隆


3
什么可以用于深度克隆?
DB

15

按照bertandg的指示使用lodash。angular不再使用此方法的原因是因为angular 1是一个独立的框架,并且外部库经常遇到与angular执行上下文有关的问题。Angular 2没有这个问题,因此请使用所需的任何库。

https://lodash.com/docs#cloneDeep


8

如果要复制类实例,则也可以使用Object.assign,但需要将新实例作为第一个参数(而不是{})传递:

class MyClass {
    public prop1: number;
    public prop2: number;

    public summonUnicorn(): void {
        alert('Unicorn !');
    }
}

let instance = new MyClass();
instance.prop1 = 12;
instance.prop2 = 42;

let wrongCopy = Object.assign({}, instance);
console.log(wrongCopy.prop1); // 12
console.log(wrongCopy.prop2); // 42
wrongCopy.summonUnicorn() // ERROR : undefined is not a function

let goodCopy = Object.assign(new MyClass(), instance);
console.log(goodCopy.prop1); // 12
console.log(goodCopy.prop2); // 42
goodCopy.summonUnicorn() // It works !

8

我找到的最简单的解决方案是:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

*重要步骤: 您必须安装lodash才能使用此功能(其他答案尚不清楚):

$ npm install --save lodash

$ npm install --save @types/lodash

然后将其导入您的ts文件中:

import * as _ from "lodash";

7

正如其他人已经指出的那样,使用lodash或下划线可能是最好的解决方案。但是,如果您不需要这些库来做其他任何事情,则可以使用以下代码:

  function deepClone(obj) {

    // return value is input is not an Object or Array.
    if (typeof(obj) !== 'object' || obj === null) {
      return obj;    
    }

    let clone;

    if(Array.isArray(obj)) {
      clone = obj.slice();  // unlink Array reference.
    } else {
      clone = Object.assign({}, obj); // Unlink Object reference.
    }

    let keys = Object.keys(clone);

    for (let i=0; i<keys.length; i++) {
      clone[keys[i]] = deepClone(clone[keys[i]]); // recursively unlink reference to nested objects.
    }

    return clone; // return unlinked clone.

  }

那就是我们决定要做的。


1
//要取消链接日期,我们可以添加:if(Object.prototype.toString.call(obj)==='[object Date]'){return new Date(obj.getTime()); }
A_J

1
或使用实例类型检查日期-if(obj instanceof Date){返回新的Date(obj.getTime())}
Anoop Isaac

0

我只需要通过我的应用程序“模型”(原始后端数据转换为对象)就可以使用此功能。因此,我最终使用了Object.create(从指定的原型创建新对象)和Object.assign(对象之间的复制属性)的组合。需要手动处理深层副本。我为此创建了要点


0

发生了同样的问题,并且不想仅使用任何插件进行深度克隆:

static deepClone(object): any {
        const cloneObj = (<any>object.constructor());
        const attributes = Object.keys(object);
        for (const attribute of attributes) {
            const property = object[attribute];

            if (typeof property === 'object') {
                cloneObj[attribute] = this.deepClone(property);
            } else {
                cloneObj[attribute] = property;
            }
        }
        return cloneObj;
    }

鸣谢:我使此功能更具可读性,请在下面检查其功能的例外情况


0

我创建了一个可用于Angular 5或更高版本的服务,它使用angular.copy ()angularjs 的基础,对我来说很好用。此外,还有其他功能,例如isUndefined,等等。希望对您有所帮助。像任何优化一样,很高兴知道。问候

import { Injectable } from '@angular/core';

@Injectable({providedIn: 'root'})
export class AngularService {

  private TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
  private stackSource = [];
  private stackDest = [];

  constructor() { }

  public isNumber(value: any): boolean {
    if ( typeof value === 'number' ) { return true; }
    else { return false; }
  }

  public isTypedArray(value: any) {
    return value && this.isNumber(value.length) && this.TYPED_ARRAY_REGEXP.test(toString.call(value));
  }

  public isArrayBuffer(obj: any) {
    return toString.call(obj) === '[object ArrayBuffer]';
  }

  public isUndefined(value: any) {return typeof value === 'undefined'; }

  public isObject(value: any) {  return value !== null && typeof value === 'object'; }

  public isBlankObject(value: any) {
    return value !== null && typeof value === 'object' && !Object.getPrototypeOf(value);
  }

  public isFunction(value: any) { return typeof value === 'function'; }

  public setHashKey(obj: any, h: any) {
    if (h) { obj.$$hashKey = h; }
    else { delete obj.$$hashKey; }
  }

  private isWindow(obj: any) { return obj && obj.window === obj; }

  private isScope(obj: any) { return obj && obj.$evalAsync && obj.$watch; }


  private copyRecurse(source: any, destination: any) {

    const h = destination.$$hashKey;

    if (Array.isArray(source)) {
      for (let i = 0, ii = source.length; i < ii; i++) {
        destination.push(this.copyElement(source[i]));
      }
    } else if (this.isBlankObject(source)) {
      for (const key of Object.keys(source)) {
        destination[key] = this.copyElement(source[key]);
      }
    } else if (source && typeof source.hasOwnProperty === 'function') {
      for (const key of Object.keys(source)) {
        destination[key] = this.copyElement(source[key]);
      }
    } else {
      for (const key of Object.keys(source)) {
        destination[key] = this.copyElement(source[key]);
      }
    }
    this.setHashKey(destination, h);
    return destination;
  }

  private copyElement(source: any) {

    if (!this.isObject(source)) {
      return source;
    }

    const index = this.stackSource.indexOf(source);

    if (index !== -1) {
      return this.stackDest[index];
    }

    if (this.isWindow(source) || this.isScope(source)) {
      throw console.log('Cant copy! Making copies of Window or Scope instances is not supported.');
    }

    let needsRecurse = false;
    let destination = this.copyType(source);

    if (destination === undefined) {
      destination = Array.isArray(source) ? [] : Object.create(Object.getPrototypeOf(source));
      needsRecurse = true;
    }

    this.stackSource.push(source);
    this.stackDest.push(destination);

    return needsRecurse
      ? this.copyRecurse(source, destination)
      : destination;
  }

  private copyType = (source: any) => {

    switch (toString.call(source)) {
      case '[object Int8Array]':
      case '[object Int16Array]':
      case '[object Int32Array]':
      case '[object Float32Array]':
      case '[object Float64Array]':
      case '[object Uint8Array]':
      case '[object Uint8ClampedArray]':
      case '[object Uint16Array]':
      case '[object Uint32Array]':
        return new source.constructor(this.copyElement(source.buffer), source.byteOffset, source.length);

      case '[object ArrayBuffer]':
        if (!source.slice) {
          const copied = new ArrayBuffer(source.byteLength);
          new Uint8Array(copied).set(new Uint8Array(source));
          return copied;
        }
        return source.slice(0);

      case '[object Boolean]':
      case '[object Number]':
      case '[object String]':
      case '[object Date]':
        return new source.constructor(source.valueOf());

      case '[object RegExp]':
        const re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
        re.lastIndex = source.lastIndex;
        return re;

      case '[object Blob]':
        return new source.constructor([source], {type: source.type});
    }

    if (this.isFunction(source.cloneNode)) {
      return source.cloneNode(true);
    }
  }

  public copy(source: any, destination?: any) {

    if (destination) {
      if (this.isTypedArray(destination) || this.isArrayBuffer(destination)) {
        throw console.log('Cant copy! TypedArray destination cannot be mutated.');
      }
      if (source === destination) {
        throw console.log('Cant copy! Source and destination are identical.');
      }

      if (Array.isArray(destination)) {
        destination.length = 0;
      } else {
        destination.forEach((value: any, key: any) => {
          if (key !== '$$hashKey') {
            delete destination[key];
          }
        });
      }

      this.stackSource.push(source);
      this.stackDest.push(destination);
      return this.copyRecurse(source, destination);
    }

    return this.copyElement(source);
  }
}


0

我和您都遇到了angular.copy和angular.expect工作的问题,因为它们在不添加一些依赖项的情况下不会复制对象或创建对象。我的解决方案是这样的:

  copyFactory = (() ->
    resource = ->
      resource.__super__.constructor.apply this, arguments
      return
    this.extendTo resource
    resource
  ).call(factory)

0
let newObj = JSON.parse(JSON.stringify(obj))

JSON.stringify()方法将JavaScript对象或值转换为JSON字符串


2
上面已经详尽地说过这是最糟糕的治疗方法!
亚历山德罗

0

您可以像这样克隆数组

 this.assignCustomerList = Object.assign([], this.customerList);

并像克隆对象

this.assignCustomer = Object.assign({}, this.customer);

0

如果您尚未使用lodash,则我不建议仅为此一种方法安装它。我建议改用更狭specialized的专业库,例如“ clone”:

npm install clone
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.