3

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/.

Gaurav Mantri
  • 128,066
  • 12
  • 206
  • 241
  • 1
    Arrow functions use lexical `this`. Your object has a property called `getLabel` that gets copied to your new child when you call `Object.assign`, that property contains a function that has a lexical `this` reference to the original Parent. If you used a normal function instead of an arrow function you would not see that behaviour. – Paul Aug 22 '16 at 19:15
  • 1
    By the way, `super(label)` already assigns `this.label`, so you don't need `this.label = label`. – Oriol Aug 22 '16 at 19:19
  • @Oriol...Thanks! It was just me trying to come up with an example for this question. – Gaurav Mantri Aug 22 '16 at 19:20

1 Answers1

7

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"
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 3
    This is the second question I've seen today where the problem was due to use of an arrow function. I guess people are falling in love with its simpler syntax, and not realizing that it has this significant difference in behavior. – Barmar Aug 22 '16 at 19:26
  • It's great for callback functions, but not for object methods. – Barmar Aug 22 '16 at 19:27
  • 1
    @Barmar: That's why I created http://stackoverflow.com/q/34361379/218196, but it's still specific (e.g. it doesn't fit this use case personally). Personally I would love to be able to close every question that misuses arrow functions as a duplicate of a single question, but that's probably just wishful thinking. – Felix Kling Aug 22 '16 at 19:28