I wrote some code:
class Base { // Default value myColor = 'blue'; constructor() { console.log(this.myColor); } } class Derived extends Base { myColor = 'red'; } // Prints "blue", expected "red" const x = new Derived();
I was expecting my derived class field initializer to run before the base class constructor. Instead, the derived class doesn't change the myColor
property until after the base class constructor runs, so I observe the wrong values in the constructor.
Is this a bug? What's wrong? Why does this happen? What should I do instead?
In C++, the constructor of a base class will not be inherited to a derived class, and the constructor of the base class will be called when we create an object of derived class.
In inheritance, the derived class inherits all the members(fields, methods) of the base class, but derived class cannot inherit the constructor of the base class because constructors are not the members of the class.
No, you cannot access any derived class members using base class pointer even pointing to a derived class instance. However you can access those values though methods of derived class.
The derived class takes the responsibility of supplying the initial values to its base class. The constructor of the derived class receives the entire list of required values as its argument and passes them on to the base constructor in the order in which they are declared in the derived class.
When you create an object from a derived class, it uses the constructor for both that class and its base class. So, if you add a custom constructor to your base class, you need to have a way for the derived class to pass values to it. If you think about this, it makes sense why this needs to be done.
How a child (derived) class calls the constructor of its base class Adding a constructor to a base class is a little more involved than adding one to a class that doesn’t have any derived (child) classes. When you create an object from a derived class, it uses the constructor for both that class and its base class.
A derived class is a class which takes some properties from its base class. It is true that a pointer of one class can point to other class, but classes must be a base and derived class, then it is possible. To access the variable of the base class, base class pointer will be used.
The derived class can have more functionality with respect to the Base class and can easily access the Base class. A Derived class is also called a child class or subclass. class BaseClass { // members.... // member function } class DerivedClass : public BaseClass { // members.... // member function }
First up, this is not a bug in TypeScript, Babel, or your JS runtime.
The first follow-up you might have is "Why not do this correctly!?!?". Let's examine the specific case of TypeScript emit. The actual answer depends on what version of ECMAScript we're emitting class code for.
Let's examine the code emitted by TypeScript for ES3 or ES5. I've simplified + annotated this a bit for readability:
var Base = (function () { function Base() { // BASE CLASS PROPERTY INITIALIZERS this.myColor = 'blue'; console.log(this.myColor); } return Base; }()); var Derived = (function (_super) { __extends(Derived, _super); function Derived() { // RUN THE BASE CLASS CTOR _super(); // DERIVED CLASS PROPERTY INITIALIZERS this.myColor = 'red'; // Code in the derived class ctor body would appear here } return Derived; }(Base));
The base class emit is uncontroversially correct - the fields are initialized, then the constructor body runs. You certainly wouldn't want the opposite - initializing the fields before running the constructor body would mean you couldn't see the field values until after the constructor, which is not what anyone wants.
Is the derived class emit correct?
Many people would argue that the derived class emit should look like this:
// DERIVED CLASS PROPERTY INITIALIZERS this.myColor = 'red'; // RUN THE BASE CLASS CTOR _super();
This is super wrong for any number of reasons:
'red'
for myColor
will be immediately overwritten by the base class value 'blue'On that last point, consider this code:
class Base { thing = 'ok'; getThing() { return this.thing; } } class Derived extends Base { something = this.getThing(); }
If the derived class initializers ran before the base class initializers, Derived#something
would always be undefined
, when clearly it should be 'ok'
.
Many other people would argue that a nebulous something else should be done so that Base
knows that Derived
has a field initializer.
You can write example solutions that depend on knowing the entire universe of code to be run. But TypeScript / Babel / etc cannot guarantee that this exists. For example, Base
can be in a separate file where we can't see its implementation.
If you didn't already know this, it's time to learn: classes are not a TypeScript feature. They're part of ES6 and have defined semantics. But ES6 classes don't support field initializers, so they get transformed to ES6-compatible code. It looks like this:
class Base { constructor() { // Default value this.myColor = 'blue'; console.log(this.myColor); } } class Derived extends Base { constructor() { super(...arguments); this.myColor = 'red'; } }
Instead of
super(...arguments); this.myColor = 'red';
Should we have this?
this.myColor = 'red'; super(...arguments);
No, because it doesn't work. It's illegal to refer to this
before invoking super
in a derived class. It simply cannot work this way.
The TC39 committee that controls JavaScript is investigating adding field initializers to a future version of the language.
You can read about it on GitHub or read the specific issue about initialization order.
All OOP languages have a general guideline, some enforced explicitly, some implicitly by convention:
Do not call virtual methods from the constructor
Examples:
In JavaScript, we have to expand this rule a little
Do not observe virtual behavior from the constructor
and
Class property initialization counts as virtual
The standard solution is to transform the field initialization to a constructor parameter:
class Base { myColor: string; constructor(color: string = "blue") { this.myColor = color; console.log(this.myColor); } } class Derived extends Base { constructor() { super("red"); } } // Prints "red" as expected const x = new Derived();
You can also use an init
pattern, though you need to be cautious to not observe virtual behavior from it and to not do things in the derived init
method that require a complete initialization of the base class:
class Base { myColor: string; constructor() { this.init(); console.log(this.myColor); } init() { this.myColor = "blue"; } } class Derived extends Base { init() { super.init(); this.myColor = "red"; } } // Prints "red" as expected const x = new Derived();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With