Những gì thay thế cho angular.copy trong Angular


134

Làm thế nào tôi có thể sao chép một đối tượng và mất tham chiếu của nó trong Angular?

Với AngularJS, tôi có thể sử dụng angular.copy(object), nhưng tôi gặp một số lỗi khi sử dụng trong Angular.

NGOẠI TRỪ: ReferenceError: angularkhông được xác định


Kiểm tra giải pháp này có thể hữu ích: Liên kết
Nourdine Alouane

Trong nhiều tình huống, bạn có thể muốn sử dụng .copy()nhưng thực sự sẽ không cần đến nó. Trong các dự án AngJS1 khác nhau mà tôi đã thấy, đó là một sự quá mức, trong đó một bản sao thủ công của các cấu trúc con có liên quan sẽ được tạo ra cho một mã sạch hơn. Có lẽ đó là một phần của quyết định không thực hiện nó bởi nhóm Angular.
phil294

nhân tiện, có liên quan (và cũng chưa được trả lời): stackoverflow.com/questions/41124528/NH
phil294

Câu trả lời:


179

Giả sử bạn đang sử dụng ES6, bạn có thể sử dụng var copy = Object.assign({}, original). Hoạt động trong các trình duyệt hiện đại; nếu bạn cần hỗ trợ các trình duyệt cũ hơn, hãy kiểm tra polyfill này

cập nhật:

Với TypeScript 2.1+, ký hiệu lan truyền đối tượng ES6 có sẵn:

const copy = { ...original }

75
Lưu ý rằng angular.copy()tạo ra một bản sao sâu trái với Object.assign(). Nếu bạn muốn sao chép sâu, hãy sử dụng lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg

trong Webstorm tôi đã nhận được Unresolved function or method assign(); Chi tiết IDE: Bão web 2016.2. Làm thế nào tôi có thể giải quyết điều đó?
mihai

2
@meorfi Đi đến File -> Settings -> Languages & Frameworks -> Javascriptvà đặt Javascript language versionthành ECMAScript 6.0.
Siri0S

_.clone @bertrandg (giá trị) khác với angular.copy (), nó sẽ không tạo ra ví dụ mới, vì vậy như _.cloneDeep (giá trị) nó vẫn tạo ra một tài liệu tham khảo stackoverflow.com/questions/26411754/...
Zealitude

5
Ngoài ra, nếu bạn đang sao chép một mảng, hãy sử dụng:const copy = [ ...original ]
daleyjem

43

Cho đến khi chúng tôi có một giải pháp tốt hơn, bạn có thể sử dụng như sau:

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

EDIT: Làm rõ

Xin lưu ý: Giải pháp trên chỉ có nghĩa là khắc phục nhanh một lớp lót, được cung cấp tại thời điểm Angular 2 đang được phát triển tích cực. Hy vọng của tôi là cuối cùng chúng ta có thể có được một tương đương angular.copy(). Vì vậy, tôi không muốn viết hoặc nhập một thư viện nhân bản sâu.

Phương pháp này cũng có vấn đề với phân tích ngày thuộc tính (nó sẽ trở thành một chuỗi).

Vui lòng không sử dụng phương pháp này trong các ứng dụng sản xuất . Chỉ sử dụng nó trong các dự án thử nghiệm của bạn - những dự án bạn đang làm để học Angular 2.


11
Điều này làm hỏng ngày của bạn và chậm như địa ngục.
LanderV

5
Tuy nhiên, không chậm như nhập toàn bộ thư viện để thực hiện một nhiệm vụ, miễn là những gì bạn đang làm khá đơn giản ...
Ian Belcher

1
Điều này thật kinh khủng, đừng bao giờ sử dụng nó
Murhaf Sousli

1
@MurhafSousli hãy cố gắng hiểu ngữ cảnh của câu trả lời này. Điều này đã được cung cấp khi Angular 2 đang được phát triển và hy vọng rằng cuối cùng chúng ta sẽ có được một hàm tương đương với angular.copy (). Để thu hẹp thời gian chờ đợi, tôi đưa ra giải pháp này dưới dạng tùy chọn tạm thời cho đến khi chúng tôi có giải pháp tốt hơn. Đây là một lớp lót với nhân bản sâu. Điều này thật kinh khủng , tôi đồng ý ... Nhưng với bối cảnh thử nghiệm tại thời điểm đó, nó không tệ đến thế.
Mani

1
@ LazarLjubenović tất nhiên là vào năm 2018 và tôi hoàn toàn đồng ý với bạn ngày hôm nay , nhưng năm 2016 webpack không bị rung cây nên bạn sẽ nhập toàn bộ thư viện trong hầu hết các trường hợp.
Ian Belcher

22

Cách khác để sao chép sâu các đối tượng có các đối tượng lồng nhau bên trong là sử dụng phương thức cloneDeep của lodash.

Đối với Angular, bạn có thể làm điều đó như thế này:

Cài đặt lodash với yarn add lodashhoặc npm install lodash.

Trong thành phần của bạn, nhập cloneDeepvà sử dụng nó:

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

Chỉ có 18kb được thêm vào bản dựng của bạn, rất xứng đáng với những lợi ích.

Tôi cũng đã viết một bài báo ở đây , nếu bạn cần hiểu rõ hơn về lý do tại sao sử dụng cloneDeep của lodash.


2
"chỉ 18kb" được thêm vào đầu ra để có thể sao chép sâu các đối tượng? JavaScript là một mớ hỗn độn.
Endrju

Sau khi đọc bài viết được tham chiếu của bạn, tôi hiểu rõ cloneDeepphương thức khởi tạo một đối tượng mới. Chúng ta vẫn nên sử dụng nó nếu chúng ta đã có một đối tượng đích?
Stephane

17

Để sao chép nông, bạn có thể sử dụng Object.assign, một tính năng ES6

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

KHÔNG sử dụng nó để nhân bản sâu


3
Những gì có thể được sử dụng để nhân bản sâu?
DB

15

Sử dụng lodash như bertandg chỉ định. Lý do angular không còn có phương pháp này nữa, bởi vì angular 1 là một khung độc lập và các thư viện bên ngoài thường gặp vấn đề với bối cảnh thực thi góc. Angular 2 không có vấn đề đó, vì vậy hãy sử dụng bất kỳ thư viện nào bạn muốn.

https://lodash.com/docs#cloneDeep


8

Nếu bạn muốn sao chép một thể hiện của lớp, bạn cũng có thể sử dụng Object.assign, nhưng bạn cần truyền một thể hiện mới làm tham số đầu tiên (thay vì {}):

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

Giải pháp đơn giản nhất tôi tìm thấy là:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

* CÁC BƯỚC QUAN TRỌNG: Bạn phải cài đặt lodash để sử dụng cái này (không rõ ràng từ các câu trả lời khác):

$ npm install --save lodash

$ npm install --save @types/lodash

và sau đó nhập nó trong tệp ts của bạn:

import * as _ from "lodash";

7

Như những người khác đã chỉ ra, sử dụng lodash hoặc gạch dưới có lẽ là giải pháp tốt nhất. Nhưng nếu bạn không cần những thư viện đó cho bất cứ điều gì khác, có lẽ bạn có thể sử dụng một cái gì đó như thế này:

  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.

  }

Đó là những gì chúng tôi quyết định làm.


1
// để hủy liên kết ngày, chúng ta có thể thêm: if (Object.prototype.toString.call (obj) === '[ngày đối tượng]') {trả về Ngày mới (obj.getTime ()); }
A_J

1
hoặc kiểm tra ngày bằng cách sử dụng loại thể hiện - if (obj instanceof Date) {trả về Ngày mới (obj.getTime ())}
Anoop Isaac

0

Tôi cần tính năng này chỉ từ mẫu 'mô hình' ứng dụng của tôi (dữ liệu phụ trợ thô được chuyển đổi thành đối tượng). Vì vậy, tôi đã kết thúc bằng cách sử dụng kết hợp Object.create (tạo đối tượng mới từ nguyên mẫu được chỉ định) và Object.assign (sao chép thuộc tính giữa các đối tượng). Cần xử lý các bản sao sâu bằng tay. Tôi đã tạo ra một ý chính cho việc này.


0

Có cùng một vấn đề và không muốn sử dụng bất kỳ plugin nào chỉ để nhân bản sâu:

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

Tín dụng: Tôi đã làm cho chức năng này dễ đọc hơn , vui lòng kiểm tra các ngoại lệ đối với chức năng của nó bên dưới


0

Tôi đã tạo ra một dịch vụ để sử dụng với Angular 5 trở lên, nó sử dụng angular.copy ()cơ sở của angularjs, nó hoạt động tốt với tôi. Ngoài ra có các chức năng khác như isUndefined, vv Tôi hy vọng nó sẽ giúp. Giống như bất kỳ tối ưu hóa, nó sẽ là tốt đẹp để biết. Trân trọng

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

Tôi cũng như bạn phải đối mặt với một vấn đề về công việc angular.copy và angular.Exect vì họ không sao chép đối tượng hoặc tạo đối tượng mà không thêm một số phụ thuộc. Giải pháp của tôi là thế này:

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

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

Các JSON.stringify()phương pháp chuyển đổi một đối tượng JavaScript hoặc giá trị cho một chuỗi JSON


2
Điều này đã được nói một cách thấu đáo ở trên rằng đây là cách tồi tệ nhất để điều trị nó!
Alessandro

0

Bạn có thể sao chép Array như

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

Và nhân bản đối tượng như

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

0

Nếu bạn chưa sử dụng lodash, tôi không khuyên bạn nên cài đặt nó chỉ cho một phương thức này. Tôi đề nghị thay vào đó là một thư viện chuyên ngành hẹp hơn, chẳng hạn như 'bản sao':

npm install clone
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.