Why does the subclass override never run when the method is an arrow class field?
The base class defines its handler as an arrow class field; the subclass tries to override it as a regular prototype method. After both instances are subscribed, only the base behavior runs for the subclass instance — investingAccount stays 0.
Explain why the subclass override is ignored, then fix it so the subclass behavior runs while the handler still keeps its this when passed as a detached callback.
class Person {
savingsAccount = 0;
getPayment = (amount) => { this.savingsAccount += amount; };
}
class Investor extends Person {
investingAccount = 0;
getPayment(amount) { // intended override
this.investingAccount += Math.floor(amount / 2);
this.savingsAccount += Math.ceil(amount / 2);
}
}
const investor = new Investor();
const handler = investor.getPayment; // passed as a detached callback
handler(1000);
console.log(investor.investingAccount); // 0 — why?
Diagnose the cause and fix it.
An arrow class field is an own instance property assigned in the constructor, not a prototype method. So investor.getPayment finds the base field first and the subclass's prototype method on Investor.prototype is shadowed — the override never runs.
- ✗Thinking a prototype method overrides an own arrow class field of the same name
- ✗Believing class fields and prototype methods live in the same place on lookup
- ✗Mixing an arrow field in the base with a prototype method in the subclass
- →Where on the lookup chain does an own class field sit versus a prototype method?
- →What does the base arrow field gain that a plain prototype method loses when detached?
Solution
An arrow class field (getPayment = (amount) => …) is an own instance property created by the field initializer in the constructor. The subclass method getPayment(amount){…} lives on Investor.prototype. When investor.getPayment is looked up, the engine finds the own property (the arrow field from Person) first and never reaches the prototype — the field shadows the method. So the base behavior runs and investingAccount stays 0.
// Fix: both are prototype methods; bind this in the constructor.
class Person {
savingsAccount = 0;
constructor() {
this.getPayment = this.getPayment.bind(this); // keep this when detached
}
getPayment(amount) { this.savingsAccount += amount; }
}
class Investor extends Person {
investingAccount = 0;
getPayment(amount) { // now truly overrides
this.investingAccount += Math.floor(amount / 2);
this.savingsAccount += Math.ceil(amount / 2);
}
}
const investor = new Investor();
const handler = investor.getPayment;
handler(1000);
console.log(investor.investingAccount); // 500
Binding in the Investor constructor binds the already-overridden method, because bind in the Person constructor reads this.getPayment, which by the time it runs resolves through the subclass prototype.