Gaurav Mantri Gaurav Mantri - 3 months ago 8
Javascript Question

Trying to understand Object.assign behavior in ES6

I'm trying to understand this behavior I am observing with ES6 classes. Consider the following code. It's very simple: I have a parent class (

Parent
) and a child class (
Child
) inheriting from it.
Parent
class has a method called
getLabel
which simply returns the label property of that class.

When I create an instance of child class, set its label and try to print it all works well.

However when I create another instance of child class by using
Object.assign
on the 1st instance, the new instance preserves the label value of the 1st instance even though I am changing it explicitly.

class Parent {
constructor(label) {
this.getLabel = () => {
return this.label;
};
this.label = label;
}
}

class Child extends Parent {
constructor(label) {
super(label);
this.label = label;
}
}

const c = new Child('Child');
console.log('c getLabel() = ' + c.getLabel());//Prints "Child"
const c1 = Object.assign(new Child('C1'), c);
c1.label = 'Child Modified';
console.log('c1 getLabel() = ' + c1.getLabel());//Prints "Child" again instead of "Child Modified".


I'm not sure why this is happening!

What I did then is changed the way I am defining
getLabel
method in
Parent
class:

class Parent2 {
constructor(label) {
this.label = label;
}

getLabel() {
return this.label;
}
}

class Child2 extends Parent2 {
constructor(label) {
super(label);
this.label = label;
}
}

const c2 = new Child2('Child 2');
console.log('c2 getLabel() = ' + c2.getLabel());//Prints "Child 2" as expected.
const c3 = Object.assign(new Child2('C3'), c2);
c3.label = 'Child 2 Modified';
console.log('c3 getLabel() = ' + c3.getLabel());//Prints "Child 2 Modified" as expected.


I would appreciate if someone can explain these two different behaviors.

Here's the ES6 Fiddle for the code above: http://www.es6fiddle.net/is6ex359/.

Answer

That's because the getLabel is defined in each instance, it's not shared in the prototype. And since you define it using arrow functions, it does not define a local binding for this, the this value will be the one of the constructor.

Then, when you use Object.assign, c1 receives the method of c, and the this value will be c, even if you call it on c1. So you get c.label, which is still 'Child'.

So you should avoid arrow functions. I recommend defining the method in the prototype:

class Parent {
  constructor(label) {
    this.label = label;
  }
  getLabel() {
    return this.label;
  }
}
class Child extends Parent {
  constructor(label) {
    super(label);
    this.label = label;
  }
}
const c = new Child('Child');
console.log('c getLabel() = ' + c.getLabel()); // "Child"
const c1 = Object.assign(new Child('C1'), c);
c1.label = 'Child Modified';
console.log('c1 getLabel() = ' + c1.getLabel()); // "Child Modified"

(Note Object.assign does not assign getLabel because it's inherited, but c1.getLabel ==== c.getLabel anyways)

Or in the constructor, but using a function expression:

class Parent {
  constructor(label) {
    this.getLabel = function() {
      return this.label;
    };
    this.label = label;
  }
}
class Child extends Parent {
  constructor(label) {
    super(label);
    this.label = label;
  }
}
const c = new Child('Child');
console.log('c getLabel() = ' + c.getLabel()); // "Child"
const c1 = Object.assign(new Child('C1'), c);
c1.label = 'Child Modified';
console.log('c1 getLabel() = ' + c1.getLabel()); // "Child Modified"

Comments