Search code examples
javascriptinheritancecallbackbindes6-class

What happens when you override an arrow function with a named function?


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


Solution

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