網站搜尋

javascript - typescript - 複製物件


我有一個超類,它是許多子類(Customer、Product、ProductCategory……)的父類(Entity)

我正在尋找在 Typescript 中動態複製包含不同子物件的物件的方法。

例如:一個 Customer 擁有不同的 Product,這些 Product 有各自的 ProductCategory

var cust:Customer  = new Customer ();

cust.name = "someName";
cust.products.push(new Product(someId1));
cust.products.push(new Product(someId2));

為了複製我創建的整個物件樹,我在 Entity 中創建了一個函數

public clone():any {
    var cloneObj = new this.constructor();
    for (var attribut in this) {
        if(typeof this[attribut] === "object"){
           cloneObj[attribut] = this.clone();
        } else {
           cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

當 new 被轉譯為 JavaScript 時會引發以下錯誤:error TS2351: 無法對缺少呼叫或建構簽名的表達式使用 'new'。

雖然這個腳本可以運作,我想要消除轉譯錯誤

答案#1

解決特定問題

你可以使用類型斷言來告訴編譯器你更清楚:

public clone(): any {
    var cloneObj = new (this.constructor() as any);
    for (var attribut in this) {
        if (typeof this[attribut] === "object") {
            cloneObj[attribut] = this[attribut].clone();
        } else {
            cloneObj[attribut] = this[attribut];
        }
    }
    return cloneObj;
}

克隆

截至 2022 年,有一個提議允許 structuredClone 深度複製多種類型。

const copy = structuredClone(value)

對於你可以用這個在什麼東西上,有一些限制。

請記住,有時候自己編寫映射比完全動態化更好。然而,你可以使用一些「克隆」技巧,這些技巧會帶來不同的效果。

我將使用以下程式碼來處理所有後續的範例:

class Example {
  constructor(public type: string) {

  }
}

class Customer {
  constructor(public name: string, public example: Example) {

  }

  greet() {
    return 'Hello ' + this.name;
  }
}

var customer = new Customer('David', new Example('DavidType'));

選項 1:傳播

屬性: 有
方法: 無
深度複製: 無

var clone = { ...customer };

alert(clone.name + ' ' + clone.example.type); // David DavidType
//alert(clone.greet()); // Not OK

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

選項 2:Object.assign

屬性: 有
方法: 無
深度複製: 無

var clone = Object.assign({}, customer);

alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // Not OK, although compiler won't spot it

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David SteveType

選項 3:Object.create

屬性: 繼承
方法: 繼承
深層複製: 淺層繼承(深層更改會影響原始和克隆)

var clone = Object.create(customer);
    
alert(clone.name + ' ' + clone.example.type); // David DavidType
alert(clone.greet()); // OK

customer.name = 'Misha';
customer.example = new Example("MishaType");

// clone sees changes to original 
alert(clone.name + ' ' + clone.example.type); // Misha MishaType

clone.name = 'Steve';
clone.example.type = 'SteveType';

// original sees changes to clone
alert(customer.name + ' ' + customer.example.type); // Misha SteveType

選項 4:深拷貝函數

屬性:是
方法:否
深層複製:是

function deepCopy(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = deepCopy(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = deepCopy(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

var clone = deepCopy(customer) as Customer;

alert(clone.name + ' ' + clone.example.type); // David DavidType
// alert(clone.greet()); // Not OK - not really a customer

clone.name = 'Steve';
clone.example.type = 'SteveType';

alert(customer.name + ' ' + customer.example.type); // David DavidType

答案#2

  1. 使用展開運算子 ...

     const obj1 = { param: "value" };
     const obj2 = { ...obj1 };
    

展開運算子會取出 obj1 的所有欄位並將它們展開到 obj2 上。結果你會得到一個具有新參考的新的物件,且欄位與原始物件相同。

請記住這是淺拷貝,這意味著如果對象是嵌套的,那麼其嵌套的複合參數將以相同的引用存在於新對象中。

  1. Object.assign()
     const obj1={ param: "value" };
     const obj2:any = Object.assign({}, obj1);
    

Object.assign 會創建真正的複製,但僅限於自身屬性,因此原型中的屬性將不會存在於複製對象中。它也只是淺層複製。


  1. Object.create()
     const obj1={ param: "value" };
     const obj2:any = Object.create(obj1);
    

Object.create 並沒有真正複製,它是從原型創建物件。因此,如果物件應該複製原始類型屬性,可以使用它,因為原始類型屬性的賦值不是通過引用完成的。

Object.create 的優點是,原型中宣告的任何函數都可以在我們新創建的物件中使用。


關於淺層複製的幾件事

淺拷貝將舊物件的所有欄位放入新物件,但這也意味著如果原始物件有複合類型欄位(物件、陣列等),那麼這些欄位將以相同的引用放入新物件。在原始物件中變更這些欄位將會反映在新物件中。

這看起來可能像是一個陷阱,但實際上需要整個複雜對象被複製的情況很少。淺層複製將重用大部分內存,這意味著與深層複製相比,它的開銷非常低。


深層複製

展開運算符對於深拷貝非常方便。

const obj1 = { param: "value", complex: { name: "John"}}
const obj2 = { ...obj1, complex: {...obj1.complex}};

上面的程式碼創建了 obj1 的深層副本。複合欄位 "complex" 也被複製到 obj2 中。對欄位 "complex" 的變更將不會反映在副本上。

答案#3

試試這個:

let copy = (JSON.parse(JSON.stringify(objectToCopy)));

這是一個不錯的解決方案,直到你使用非常大的物件或你的物件具有不可序列化的屬性時。

為了保持類型安全,你可以在你想要複製的類別中使用複製函數:

getCopy(): YourClassName{
    return (JSON.parse(JSON.stringify(this)));
}

或者以靜態的方式:

static createCopy(objectToCopy: YourClassName): YourClassName{
    return (JSON.parse(JSON.stringify(objectToCopy)));
}

答案#4

TypeScript/JavaScript 有自己的淺拷貝運算子:

let shallowClone = { ...original };

答案#5

對於可序列化的深層複製,帶有類型資訊的是,

export function clone<T>(a: T): T {
  return JSON.parse(JSON.stringify(a));
}

答案#6

使用 TypeScript 2.1 引入的「物件展開」可以輕鬆獲取淺拷貝

這個 TypeScript: let copy={ ...original };

產生這個 JavaScript:

var __assign = (this && this.__assign) || Object.assign || function(t) {
    for (var s, i = 1, n = arguments.length; i < n; i++) {
        s = arguments[i];
        for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
            t[p] = s[p];
    }
    return t;
};
var copy = __assign({}, original);

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html

答案#7

這裡是在 TypeScript 中的 deepCopy 實現(程式碼中不包含 any):

const deepCopy = <T, U = T extends Array<infer V> ? V : never>(source: T ): T => {
  if (Array.isArray(source)) {
    return source.map(item => (deepCopy(item))) as T & U[]
  }
  if (source instanceof Date) {
    return new Date(source.getTime()) as T & Date
  }
  if (source && typeof source === 'object') {
    return (Object.getOwnPropertyNames(source) as (keyof T)[]).reduce<T>((o, prop) => {
      Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!)
      o[prop] = deepCopy(source[prop])
      return o
    }, Object.create(Object.getPrototypeOf(source)))
  }
  return source
}

答案#8

將 "lodash.clonedeep": "^4.5.0" 添加到你的 package.json。然後像這樣使用:

import * as _ from 'lodash';

...

const copy = _.cloneDeep(original)