1

I have following code, written in ES6 plus some Stage 3 proposals:

class Parent {
  constructor(x){
    this.x = x;
    this.otherProperty = "preserve across copy";
  }

  printX=()=>{
    console.log(this.x);
  }

  squareX=()=>{
    this.x = this.x *this.x; 
  }
}

class Child extends Parent {
  constructor(x){
    super(x);
  }  
}

const c = new Child(20);
const copy = {...c, x: 10};

console.log(JSON.stringify(c));
c.squareX();
console.log(JSON.stringify(c));

console.log(JSON.stringify(copy));
copy.squareX();
console.log(JSON.stringify(copy));

Live demo: https://jsbin.com/wuqenabaya/1/edit?js,console

The idea is to create a copy of the instance c while updating some of it's properties. The output of this code is:

{x:  20, otherProperty: "preserve across copy"}
{x: 400, otherProperty: "preserve across copy"}
{x:  10, otherProperty: "preserve across copy"}
{x:  10, otherProperty: "preserve across copy"}

So as you can see the copy.squareX() does not update the instance copy. The issue is that the function squareX() is still bound to the old instance c.

What I want is to have the last call to squareX() to update the instance copy. How can this be achieved?

EDIT: I'm using Babel with following plugins, to allow the use of the new JS features (spread, function props).

{
  "presets": [
    ["es2015", { "modules": false }],
    "stage-0",
    "react"
  ],
  "plugins": [
    "react-hot-loader/babel",
    "transform-object-rest-spread",
    "transform-class-properties",
    "transform-flow-strip-types"
  ]
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
jnovacho
  • 2,825
  • 6
  • 27
  • 44
  • 1
    Note: "ES6" (ES2015) doesn't have spread properties. In fact, they aren't even in ES2017. They may be in ES2018; currently a [stage 3 proposal](https://github.com/tc39/proposal-object-rest-spread). – T.J. Crowder Sep 11 '17 at 08:35
  • is it an option to update the classes? i'm thinking of something like a static factory function that as argument accepts an class instance, reads the needed properties, generates a new instance, updates those properties, and returns the new instance. instead of cloning the old instance via object literal – mbehzad Sep 11 '17 at 08:39
  • Similarly, the [field declaration syntax](https://github.com/tc39/proposal-class-fields) you're using is *also* not yet in any spec, and also at Stage 3. – T.J. Crowder Sep 11 '17 at 08:40
  • @T.J.Crowder: I'm actually using Babel. I have update the answer to reflect this fact. – jnovacho Sep 11 '17 at 08:46
  • Well [just don't use arrow functions when you don't want your methods be bound](https://stackoverflow.com/q/34361379/1048572) - the code would totally work with `function` expressions. – Bergi Sep 11 '17 at 10:09

1 Answers1

2

There are a few problems with trying to use spread properties for this, not least that you'll end up with an object whose prototype is Object.prototype, not Child.prototype.

You're also using field syntax for methods, which makes those fields own properties of the object, not prototype properties, which there doesn't seem to be any reason for in this case; just use method syntax.

To do the copy, give yourself a "copy constructor" branch in your constructor:

constructor(x, ...more){
  if (x instanceof Child) {
    // The copy constructor branch
    super(x.x);
    Object.assign(this, x, ...more);
  } else {
    super(x);
  }
}  

Or if you prefer, just do it where you create copy:

const copy = Object.assign(new Child(c.x), c, {x: 10});

In either case, if you choose to keep using fields rather than methods, you'll have to adjust things since otherwise you'll copy squareX and printX as well.

Live Example using methods and the copy constructor:

class Parent {
  constructor(x){
    this.x = x;
    this.otherProperty = "preserve across copy";
  }

  printX() {
    console.log(this.x);
  }

  squareX() {
    this.x = this.x *this.x; 
  }
}

class Child extends Parent {
  constructor(x, ...more){
    if (x instanceof Child) {
      super(x.x);
      Object.assign(this, x, ...more);
    } else {
      super(x);
    }
  }  
}

const c = new Child(20);
const copy = new Child(c, {x: 10});

console.log(JSON.stringify(c));
c.squareX();
console.log(JSON.stringify(c));

console.log(JSON.stringify(copy));
copy.squareX();
console.log(JSON.stringify(copy));
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875