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"
}
}