如何深度克隆用户定义类的对象并保留类的对象方法?
例如,我有一个名为Schedule
的Object类,其成员为days: number[]
,函数为getWeekdays()
因此,如果我想创建一个新的Schedule
对象,它将是具有克隆属性的现有Schedule
的克隆,并且还具有getWeekdays()
函数,我该怎么做?我尝试了Object.assign()
,但只有浅版本days
而且我知道JSON.parse()
无法工作,因为我没有获得对象方法。我尝试了lodash的_.cloneDeep()
但不幸的是,创建的对象缺少对象方法。
答案 0 :(得分:1)
您需要首先将对象序列化为JSON,对结果进行深层克隆,然后将其反序列化回类对象。您可以使用如下库:https://github.com/typestack/class-transformer
所以最终看起来像这样:
import { classToPlain, plainToClass } from "class-transformer";
let a = new Schedule();
let aSerialized = classToPlain(a);
let b = plainToClass(Schedule, aSerialized);
或者您可以使用classToClass
方法:
import { classToClass } from "class-transformer";
let b = classToClass(a);
棘手的是,您必须使用上述库中的一些注释来对类进行注释,但是我认为没有更好的方法可以做到这一点。
答案 1 :(得分:0)
尝试here
中的copy
功能
// from https://www.codementor.io/avijitgupta/deep-copying-in-js-7x6q8vh5d
function copy(o) {
var output, v, key;
output = Array.isArray(o) ? [] : {};
for (key in o) {
v = o[key];
output[key] = (typeof v === "object") ? copy(v) : v;
}
return output;
}
var Event = /** @class */ (function () {
function Event(name) {
this.name = name;
}
Event.prototype.getName = function () {
return "Event " + this.name;
};
return Event;
}());
var Schedule = /** @class */ (function () {
function Schedule() {
}
Schedule.prototype.getWeekdays = function () {
return this.weekDays;
};
return Schedule;
}());
var schedule = new Schedule();
schedule.days = [3, 11, 19];
schedule.weekDays = [1, 2, 3];
schedule.event = new Event("Event");
var clone = copy(schedule);
console.log(clone);
答案 2 :(得分:0)
Object.assign()
将其指定为函数属性, getWeekdays()
将保留=
函数:
class Schedule {
public days: Array<number> = [];
public getWeekdays = (): Array<number> => {
return this.days;
}
}
const clone = Object.assign({}, new Schedule()); // clone = { days: [], getWeekdays: [Function] }
答案 3 :(得分:0)
Object.assign()
绑定到对象而不是其原型,则将保留getWeekdays()
方法:
⚠️将方法直接绑定到对象而不是对象的原型通常被认为是一种反模式-特别是在性能优先级更高的情况下-因为N
Schedule
个引用N separategetWeekend()
函数,而不是引用原本由原型共享的单个getWeekend()
函数。
第一种方法是使用箭头函数在class
定义中声明您的方法,如下所示:
class Schedule {
public days: Array<number> = [];
public getWeekdays = (): Array<number> => {
return this.days;
}
}
const clone = Object.assign({}, new Schedule());
此方法起作用的原因有两个:
Object.assign()
复制对象的自身属性,但不复制其继承的属性。如果运行console.log(new Schedule());
,则可以看到第一个作用点:
// with arrow function:
▼ Schedule {days: Array(0), getWeekdays: } ⓘ
▷ days: Array(0) []
▷ getWeekdays: () => { … }
▷ __proto__: Object { constructor: … }
// without arrow function:
▼ Schedule { days: Array(0) } ⓘ
▷ days: Array(0) []
▼ __proto__: Object { constructor: , getWeekdays: }
▷ constructor: class Schedule { … }
▷ getWeekdays: getWeekdays() { … }
▷ __proto__: Object { constructor: , __defineGetter__: , __defineSetter__: , … }
static
方法有何不同? static
方法不是对象原型的绑定,而是绑定到class
本身,后者是原型的构造函数:
class Schedule {
public static days: Array<number> = [];
public static getWeekdays(): Array<number> {
return this.days;
}
}
const clone = Object.assign({}, new Schedule());
console.log(new Schedule());
// console
▼ Schedule {} ⓘ
▼ __proto__: Object { constructor: … }
▼ constructor: class Schedule { … }
[[FunctionLocation]]: internal#location
▷ [[Scopes]]: Scopes[1]
arguments: …
caller: …
▷ days: Array(0) []
▷ getWeekdays: getWeekdays() { … }
length: 0
name: "Schedule"
▷ prototype: Object { constructor: … }
▷ __proto__: function () { … }
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
这意味着static
方法不能直接绑定到对象。如果尝试,将得到以下TSError:
~/dev/tmp/node_modules/ts-node/src/index.ts:261
return new TSError(diagnosticText, diagnosticCodes)
^
TSError: ⨯ Unable to compile TypeScript:
index.ts(14,14): error TS2334: 'this' cannot be referenced in a static property initializer.
at createTSError (~/dev/tmp/node_modules/ts-node/src/index.ts:261:12)
at getOutput (~/dev/tmp/node_modules/ts-node/src/index.ts:367:40)
at Object.compile (~/dev/tmp/node_modules/ts-node/src/index.ts:558:11)
at Module._compile (~/dev/tmp/node_modules/ts-node/src/index.ts:439:43)
at internal/modules/cjs/loader.js:733:10
at Object..ts (~/dev/tmp/node_modules/ts-node/src/index.ts:442:12)
at Module.load (internal/modules/cjs/loader.js:620:32)
at tryModuleLoad (internal/modules/cjs/loader.js:560:12)
at Function._load (internal/modules/cjs/loader.js:552:3)
at Function.runMain (internal/modules/cjs/loader.js:775:12)
.bind()
在构造函数中箭头函数(包括在class
方法定义中使用的箭头函数)是ES6的一项功能,它针对this
关键字的行为为函数声明表达式提供了更为简洁的语法。与常规函数不同,箭头函数使用其包围的词法范围的this
值,而不是根据其调用的上下文来建立自己的this
值。他们也不会收到自己的arguments
对象(或super
或new.target
)。
在ES6之前,如果需要在用作回调的方法中使用this
,则必须将主机对象的this
的值绑定到方法的{{1 }}和this
,它返回一个更新的函数,其.bind()
的值设置为提供的值,如下所示:
this
在ES6 var clone;
function Schedule() {
this.days = [];
this.setWeekdays = function(days) {
this.days = days;
}
this.setWeekdays = this.setWeekdays.bind(this);
}
clone = Object.assign({}, new Schedule());
console.log(clone);
// console
▼ Object {days: Array(0), setWeekdays: }
▷ days:Array(0) []
▷ setWeekdays:function () { … }
▷ __proto__:Object {constructor: , __defineGetter__: , __defineSetter__: , …}
中,可以通过在构造函数中的方法上调用class
来获得相同的结果:
.bind()
⚠️也不一定推荐,因为您最终会分配通常永远不会调用的函数,如下所述。
装饰器在TypeScript中被认为是实验性功能,要求您在class Schedule {
public days: Array<number> = [];
constructor() {
this.getWeekdays = this.getWeekdays.bind(this);
}
public getWeekdays(): Array<number> {
return this.days;
}
}
const clone = Object.assign({}, new Schedule());
console.log(clone);
// console
▼ Object {days: Array(0), setWeekdays: … } ⓘ
▷ days: Array(0) []
▷ setWeekdays: function () { … }
▷ __proto__: Object { constructor: , __defineGetter__: , __defineSetter__: , … }
中将experimentalDecorators
设置为true
。
使用自动绑定修饰符将使您可以按需重新绑定tsconfig.json
方法-就像在构造函数中使用getWeekdays()
键一样,但是绑定是在调用.bind()
时发生的getWeekdays()
被调用的时间-仅以更紧凑的方式:
new Schedule()
但是,由于Decorators仍处于第2阶段,因此在TypeScript中启用装饰器只会公开4种类型的装饰器函数(即class Schedule {
public days: Array<number> = [];
@bound
public getWeekdays(): Array<number> {
return this.days;
}
}
,ClassDecorator
,PropertyDecorator
,{{1 }}。)第2阶段中建议的内置装饰器(包括@bound
)不是现成的。
要使用MethodDecorator
,您必须让Babel使用@babel/preset-typescript
和@babel/preset-stage-2
处理您的TypeScript转译。
或者,可以将此NPM程序包(某种程度上)填充此功能:
此软件包的ParameterDecorator
将@bound
方法绑定到@boundMethod
的结果对象 到其原型,但不会 >由getWeekdays()
复制:
new Schedule()
这是因为Object.assign()
装饰器会覆盖方法的// console.log(new Schedule());
▼ Schedule { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ getWeekdays: function () { … }
▼ __proto__: Object { constructor: , getWeekdays: <accessor> }
▷ constructor: class Schedule { … }
▷ getWeekdays: getWeekdays() { … }
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
// console.log(clone);
▼ Object { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ __proto__: Object { constructor: … , __defineGetter__: … , __defineSetter__: … , … }
和@boundMethod
访问器以调用get
(因为已设置这些访问器中的set
的值)到分配属性的对象),使用.bind()
将其附加到对象,然后为绑定方法返回this
,这会产生一些有趣的效果:
Object.defineProperty()
PropertyDescriptor
不起作用的原因实际上是双重的:
const instance = new Schedule();
console.log('instance:', instance);
console.log('\ninstance.hasOwnProperty(\'getWeekdays\'):', instance.hasOwnProperty('getWeekdays'));
console.log('\ninstance.getWeekdays():', instance.getWeekdays());
console.log('\ninstance.hasOwnProperty(\'getWeekdays\'):', instance.hasOwnProperty('getWeekdays'));
// console
instance:
▼ Schedule { days: Array(0) } ⓘ
▷ days: Array(0) []
▷ getWeekdays: function () { … }
▷ __proto__: Object { constructor: , getWeekdays: <accessor> }
instance.hasOwnProperty('getWeekdays'): false
instance.getWeekdays():
▷ Array(0) []
instance.hasOwnProperty('getWeekdays'): true
)上调用{em> Object.assign()
,在目标对象(即[[Get]]
)上调用了new Schedule()
。 / li>
[[Set]]
用于检修{}
访问者的PropertyDescriptor
是不可枚举的。如果我们要更改最后一点并使用可枚举的访问器,则可以使@boundMethod
起作用,但是只有之后 getWeekend()
至少已被调用一次:
Object.assign()