13

I'm having trouble accessing a method in a hierarchy when each class contains a method with the same name.

class A { 
    constructor(private name: string) { }
    notify() { alert(this.name) }
}

class B extends A { 
    constructor() {
        super("AAA")
    }

    notify() {alert("B") }
}

class C extends B { 
    notify() { alert("C") }

    callA() {
        this.notify(); // this alerts "C"
        super.notify(); // this alerts "B"

        // How to call notify() of the class A so it alerts "AAA"? 
    }
}

new C().callA();
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
Milo711
  • 265
  • 1
  • 3
  • 9
  • There is no construct that allows you to do this -- but what are you trying to do specifically? You should always be careful when overriding methods. – Explosion Pills Mar 01 '18 at 18:41

3 Answers3

16

While I question the design that requires you to do this, you can easily acieve this by getting the original method of A.prototype and using call:

class C extends B { 
    notify() { alert("C") }

    callA() {
        A.prototype.notify.call(this);
    }
}
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
7

Grandparent method can be reached by climbing prototype chain up:

class C extends B { 
    notify() { alert("C") }

    callA() {
        this.notify(); // this alerts "C"
        const grandparentNotify = super.__proto__.notify;
        grandparentNotify.call(this); // this alerts "AAA"
    }
}

__proto__ is used for illustrative purposes, because the proper way to get object prototype is Object.getPrototypeOf. Notice that super.__proto__ chain for grantparent prototype may be different between implementations (e.g. TypeScript and native).

Grandparent method shouldn't be reached, because this indicates design problem; a grandchild shouldn't be aware of grandparent methods. The use of call in methods is another sign that class design went wrong.

If there's a need to use a method from another class (it doesn't really matter whether it is grandparent) in extended class, this should be done explicitly, via a mixin. Since C doesn't need all grandparent methods and needs to avoid naming collisions, a method should be assigned directly:

interface C {
    grandparentNotify(): void;
}
class C extends B { 
    notify() { alert("C") }

    callA() {
        this.notify(); // this alerts "C"
        this.grandparentNotify(); // this alerts "AAA"
    }
}
C.prototype.grandparentNotify = A.prototype.notify;

The interfaces are merged, and grandparentNotify is accepted as C method by typing system. This way looks raw, but it is idiomatic way to assign a method.

A bit more smoother way that provides some overhead but requires no interface merging is a getter:

class C extends B { 
    notify() { alert("C") }

    get grandparentNotify() {
        return A.prototype.notify;
    }

    callA() {
        this.notify(); // this alerts "C"
        this.grandparentNotify(); // this alerts "AAA"
    }
}
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
0

If you have:

class X { hey() {}}
class Y extends X { hey(){}}
class Z extends Y {hey(){}}

You can invoke X::hey from Z like this:

Z extends Y {
   sayHey() {
      X.prototype.hey.call(this)
  }

Gilbert
  • 2,699
  • 28
  • 29