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
使用展開運算子
...const obj1 = { param: "value" }; const obj2 = { ...obj1 };
展開運算子會取出 obj1 的所有欄位並將它們展開到 obj2 上。結果你會得到一個具有新參考的新的物件,且欄位與原始物件相同。
請記住這是淺拷貝,這意味著如果對象是嵌套的,那麼其嵌套的複合參數將以相同的引用存在於新對象中。
Object.assign()const obj1={ param: "value" }; const obj2:any = Object.assign({}, obj1);
Object.assign 會創建真正的複製,但僅限於自身屬性,因此原型中的屬性將不會存在於複製對象中。它也只是淺層複製。
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)
