使用对象方法深度克隆一个类对象?

时间:2018-06-11 19:24:56

标签: javascript typescript lodash

如何深度克隆用户定义类的对象并保留类的对象方法?

例如,我有一个名为Schedule的Object类,其成员为days: number[],函数为getWeekdays()

因此,如果我想创建一个新的Schedule对象,它将是具有克隆属性的现有Schedule的克隆,并且还具有getWeekdays()函数,我该怎么做?我尝试了Object.assign(),但只有浅版本days而且我知道JSON.parse()无法工作,因为我没有获得对象方法。我尝试了lodash的_.cloneDeep()但不幸的是,创建的对象缺少对象方法。

4 个答案:

答案 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 separate getWeekend()函数,而不是引用原本由原型共享的单个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对象(或supernew.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; } } ClassDecoratorPropertyDecorator,{{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()