0

I have a code like the following:

class ClickModule {
   constructor() {
      this.handleClick = this.handleClick.bind(this);
   }

   handleClick = (evt) => {}
}

class ImageModule extends ClickModule {
   handleClick(evt) {
      //some logic that never gets called
   }
}

handleClick is a function used as a callback. I know you cannot bind an arrow function, but as it is being overridden as a class method, why is it not working?

The fix was to declare the initial handleClick as

handleClick(evt) {}

But I don't understand quite well what was producing that the handleClick in ImageModule was never called.

Thanks

I tested it on this codePen https://codepen.io/mpfrancog/pen/rNZREdx

Maga
  • 53
  • 4
  • 1
    The method in the second declaration is declared on the prototype, but the arrow function (which can't be bound but can be declared in the constructor `constructor() { this.handleClick = (evt) => {}; }`) isn't. So *either* make it a class property with an anonymous arrow function assigned to it, *or* make it a method, then override it using the same means. – pilchard Mar 28 '23 at 15:23
  • @Unmitigated I added a codepen – Maga Mar 28 '23 at 15:35
  • 2
    javascript looks for properties before looking at the prototype, so the parent property is shadowing the child prototype method. – pilchard Mar 28 '23 at 15:45

1 Answers1

3

The handleClick on ImageModule isn't being used at all, because ClickModule's class property (not method) is assigned to the instance at construction time, so it takes precedence over the handleClick on the prototype the instance gets from ImageModule.prototype.

Here's what happens when you do new ImageModule:

  1. The (automatically-generated) ImageModule constructor is called.

  2. It calls ClickModule's constructor.

  3. Just before ClickModule's constructor is called, an instance is created using ImageModule.prototype as its prototype. ImageModule.prototype has handleClick from ImageModule, so for this brief moment, the instance would inherit that method. But...

  4. The code in ClickModule's constructor is called.

    • The first thing in that constructor is the class property you've defined here:
      handleClick = (evt) => {}
      
      When the class was built, that code was put in the constructor as though the constructor started with this:
      Object.defineProperty(this, "handleClick", {
           "value": (evt) => {},
           "writable": true,
           "enumerable": true,
           "configurable": true
      });
      
      That is, it puts the property on the instance (replacing any property that was there, but there wasn't in this case), so the property the instance inherits from its prototype will not be used.
    • The constructor's code continues with the bind call, using the instance (not prototype) property and then writing to it. The bind call doesn't have any effect (in this case), since it doesn't bind any arguments and attempts to bind the this value that the arrow function already closes over.

So ImageModule's handleClick isn't used at all.

If you want to have a method that subclasses can override, define a method, not a class property:

class ClickModule {
    constructor() {
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(evt) {
        // ...presumably some code here...
    }
}

class ImageModule extends ClickModule {
    handleClick(evt) {
        // ...some logic that now _will_ get called...
    }
}

That way, the bind call in ClickModule is working with the handleClick on the instance's prototype, which is the one from ImageModule.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • 1
    Thanks for the explanation, I was having trouble understanding this step by step. – Maga Mar 28 '23 at 16:15