In the following code, the base class has four properties: a getter, a field, and a couple of methods that prints out the values of the other two.
In the subclasses, the base getter can be overridden by either another getter OR a field. However, the base field can only be overridden by other fields, not a getter.
I was a little surprised by this dichotomy. Am I doing something wrong?
class A {
get foo() {
return 'A.foo-as-getter';
}
bar = 'A.bar-as-field';
go() {
console.log(this.constructor.name, 'go', this.foo, this.bar);
return this;
}
go2() {
console.log('A.go2', this.foo, this.bar);
}
}
class B extends A {
foo = 'B.foo-as-field';
get bar() {
return 'B.bar-as-getter';
}
set bar(val) {}
go2() {
console.log('B.go2', this.foo, this.bar);
}
}
class C extends A {
get foo() {
return 'C.foo-as-getter';
}
set foo(val) {}
bar = 'C.bar-as-field';
go2() {
console.log('C.go2', this.foo, this.bar);
}
}
new A().go().go2();
console.log('----');
new B().go().go2();
console.log('----');
new C().go().go2();
2
Answers
You’re not doing anything wrong; this behavior is consistent with how JavaScript handles class inheritance and property overriding.
Overriding Getters: In JavaScript, getters are just special kinds of properties that run a function when accessed. When you override a getter in a subclass with another getter or a field, you’re essentially replacing the property descriptor of that property in the subclass. This is why you can override a getter with either a field or another getter.
Overriding Fields: Fields are essentially properties initialized in the constructor. When you try to override a field with a getter, it doesn’t work as expected because the field initialization in the constructor will overwrite the getter. This is why fields can only be effectively overridden by other fields.
The behavior you’re observing is expected due to how getters and fields work in classes.
Getters: When you define a getter in a subclass, it overrides the getter in the base class. This is because getters are part of the object’s prototype, and the subclass prototype chain will point to the base class prototype. So, when you access a property that has a getter, JavaScript will look up the prototype chain and use the getter from the subclass if it exists.
Fields: Fields are not part of the prototype; they are instance variables. When you create a new instance of a subclass, the field in the subclass will shadow the field in the base class. However, you can’t override a field with a getter in the subclass because fields are initialized in the constructor, and they will shadow any getters with the same name.
Here’s a breakdown of what happens in your code:
new A().go().go2();
will print "A.getter" and "A.field" because it’s an instance of the base class.new B().go().go2();
will print "B.getter" and "A.field" forgo()
and "B.getter" and "B.field" forgo2()
. Thegetter
is overridden by a field in classB
, and thefield
is not effectively overridden because it’s a getter inB
.new C().go().go2();
will print "C.getter" and "C.field" for bothgo()
andgo2()
. Bothgetter
andfield
are effectively overridden in classC
.Some code to better explain this behavior
Result
So, you’re not doing anything wrong; this is just how JavaScript handles getters and fields in class inheritance.
The explanation is easy: a getter is defined in a prototype, but a field in an object itself. So if the same field is in an object and its prototype, the prototype isn’t used (JS doesn’t look into it because the property is already found in the object):
To override a field with a prototype’s getter you should delete this field from the base class’ object in the constructor: