George Keeling
George Keeling

Reputation: 111

copy instance of class in javascript / typescript

I have two objects inst1, inst2 which are both instances of the same class. If I use

inst2 = JSON.parse(JSON.stringify(inst1));

now if I change values of properties of inst2, values in inst1 do not change. That is great. But sadly methods of inst2 have disappeared. So if I do

inst2.method1();

I get the error "inst2.method1 is not a function"

Is there some way I can copy the values in an instance without destroying methods? (obviously I could laboriously copy each value. I am trying to avoid that because I am lazy.)

I have tried to follow typescript - cloning object but I cannot make it work-

Upvotes: 0

Views: 2217

Answers (3)

George Keeling
George Keeling

Reputation: 111

I came back to this at a convenient point and made quite a bit of progress by combining some of the above answers. The general purpose cloner was getting quite ugly (see below) and still not working (for arrays of class-objects) when I realised that it would be impossible to write a general purpose cloner.

I use the term class-object to mean an object defined by a class.

If a class-object contains a variable which itself is type class-object, call it subObj, then the general purpose cloner cannot know whether 1) it should copy subObj or 2) it should create a new instance of subObj and copy into the sub-properties. The answer depends on the meaning in the class.

In the first case above subObj. is just a pointer to another instance of subObj.

Therefore I strongly agree with the second part of Svetoslav Petkov's answer that the "class itself [should] have a clone method and be responsible for its own cloning logic.".

For what it's worth this is as far as I got with a general purpose cloner (in TypeScript). It is adapted from the other answers and creates new instances of class-objects liberally:

public clone(): any {
var cloneObj = new (this.constructor as any)() as any;
for (var attribut in this) {
  // attribut is a string which will take the values of the names of the propertirs in 'this'
  // And for example, if aNumber is a property of 'this' then
  // this['aNumber'] is the same as this.aNumber
  if (typeof this[attribut] === "object") {
    let thisAttr = this[attribut] as any;
    let cloneAttr = cloneObj[attribut] as any;
    if (this[attribut] instanceof Array) {
      for (let i in thisAttr) {
        cloneAttr[i] = thisAttr[i];         // **** will not work on arrays of objects!!
      }
      continue; // to next attrib in this
    }
    if (this[attribut] instanceof Date) {
      cloneAttr.setTime(thisAttr.getTime());
      continue; // to next attrib in this
    }
    try {
      cloneObj[attribut] = thisAttr.clone();
      //cloneObj[attribut] = this.clone();     // with this, (from https://stackoverflow.com/questions/28150967/typescript-cloning-object) stack just gets bigger until overflow
    }
    catch (err) {
      alert("Error: Object " + attribut + " does not have clone method." +
        "\nOr " + err.message);
    }
  } else {
    cloneObj[attribut] = this[attribut];
  }
}
return cloneObj;

}

Upvotes: 0

Svetoslav Petkov
Svetoslav Petkov

Reputation: 1575

Ok, I have played a little since the provided answers are not 100% clear. If you want to have a shallow copy and copy the methods too, you can use Object.create.

Again: If your object is simple enough, Object.create will be sufficient for you

const originalPerson = new Person("John");
originalPerson.address = new Address("Paris", "France");


const newPerson = Object.create(originalPerson);

/// this will be true
const isInstanceOf = newPerson instanceof Person;

//this will change the property of the new person ONLY
newPerson.name = "Peter";

//methods will work
newPerson.someMethod();

//methods will work even on nested objects instances
newPerson.address.anotherMethod();

// BUT if we change the city on any of the instances -  will change the address.city of both persons since we have done a shallow copy
newPerson.address.city = "Berlin";

I have created typescript playground (just remove the types) to show it works and the drawback with its usage - link to the playground

Another approach is the class itself to have a clone method and to be responsible for its own cloning logic. An example follows, along with a link to another playground

class Address {
  constructor(city, country) {
    this.city = city;
    this.country = country;
  }

  clone() {
    // no special logic, BUT if the address eveolves this is the place to change the clone behvaiour
    return Object.create(this);
  }

  getAddressDetails() {
    return `City: ${this.city} country ${this.country}`;
  }
}

class Person {
  constructor(name, address) {
    this.name = name;
    this.address = address;
  }

  clone() {
    const newInstance = Object.create(this);
    //clone all other class instances
    newInstance.address = this.address.clone();
    return newInstance;
  }

  getPersonDetails() {
    //calling internally address.getAddressDetails() ensures that the inner object methods are also cloned
    return `This is ${this.name}, I live in ${this.address.getAddressDetails()}`
  }
}

const originalAddress = new Address("Paris", "France");
const originalPerson = new Person("John", originalAddress);

const clonedPerson = originalPerson.clone();
clonedPerson.name = "Peter";
clonedPerson.address.city = "Berlin";
clonedPerson.address.country = "Germany";

// Log to console
console.log(`Original person: ${originalPerson.getPersonDetails()}`)
console.log(`Cloned person: ${clonedPerson.getPersonDetails()}`)

Upvotes: 1

LiamBaisley
LiamBaisley

Reputation: 35

You should use structured cloning, see this answer: How do I correctly clone a JavaScript object?

The reason that your current code isn't working is because you are parsing a stringified json object. Json stringify will remove all of the methods of an object and only stringify the objects values.

Upvotes: 0

Related Questions