What is the reason for the base class constructor not being able to access the property values of the derived class

Recently, I came across this scenario in my 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 surprised to see that the derived class field initializer did not run before the base class constructor as I had expected. This led to the base class constructor outputting the wrong values for the myColor property.

Is this a bug in the code? What could be causing this behavior? Why does it happen this way? And most importantly, what should be the correct approach to achieving the desired outcome?

Answer №1

A Feature, Not a Bug

Just to clarify, what you're experiencing is not a bug within TypeScript, Babel, or your JS runtime.

Understanding the Why

You may be wondering, "Why isn't this done correctly?" Let's delve into the specifics, focusing on TypeScript emit for different ECMAScript versions.

Downlevel Emit: ES3/ES5

When we inspect the emitted code for ES3 or ES5, it becomes apparent that the base class initialization precedes the constructor body in a logical sequence. This ensures that the fields are initialized before the constructor's execution, which is the desired behavior.

But what about the derived class?

No, the Order Matters

While some argue that the derived class initialization should occur first for various reasons, this approach is flawed. It disrupts the expected behavior and may lead to unintended consequences, especially in code like invoking base class methods relying on base class initializations.

Time Machine Required

Others propose a hypothetical solution involving informing the base class about derived class initializers. However, in a scenario where `Base` is in a separate file, this method isn't feasible for tools like TypeScript or Babel to implement.

Downlevel Emit: ES6

Classes are native to ES6, not TypeScript, and are transformed to ES6-compliant code. The sequence of super invocation and field initialization in derived classes follows a strict order due to language restrictions.

ES7+: Future Possibilities

The JavaScript committee is exploring options for field initializers in upcoming language versions, aiming to address these initialization challenges.

Object-Oriented Programming Principle Reminder

In OOP, a common guideline prohibits invoking virtual methods from constructors, extending to property initialization. This principle holds true across various languages, including JavaScript.

Potential Solutions

One recommended approach involves transforming field initialization into constructor parameters to ensure a predictable sequence of execution. Alternatively, an `init` pattern can be employed cautiously to avoid observing virtual behaviors during initialization.

Answer №2

In my opinion, there is a valid argument to classify this as a bug

When an unexpected action leads to undesired behavior, it disrupts the typical usage scenarios for class extensions. The ideal sequence of initialization steps to support your scenario would be as follows:

Base property initializers
Derived property initializers
Base constructor
Derived constructor

Challenges and Resolutions

- The TypeScript compiler currently includes property initializations in the constructor function

To address this issue, it is crucial to segregate property initializations from the invocation of constructor functions. A similar approach is adopted in C#, where base properties are initialized after derived properties, although this is deemed counterintuitive. One possible solution could involve the creation of helper classes, allowing the derived class to initialize the base class in any sequence.

class _Base {
    ctor() {
        console.log('base ctor color: ', this.myColor);
    }

    initProps() {
        this.myColor = 'blue';
    }
}
class _Derived extends _Base {
    constructor() {
        super();
    }

    ctor() {
        super.ctor();
        console.log('derived ctor color: ', this.myColor);
    }

    initProps() {
        super.initProps();
        this.myColor = 'red';
    }
}

class Base {
    constructor() {
        const _class = new _Base();
        _class.initProps();
        _class.ctor();
        return _class;
    }
}
class Derived {
    constructor() {
        const _class = new _Derived();
        _class.initProps();
        _class.ctor();
        return _class;
    }
}

// Outputs:
// "base ctor color: red"
// "derived ctor color: red"
const d = new Derived();

- Will the base constructor encounter issues when utilizing properties from the derived class?

Any functionality that fails within the base constructor can be transferred to a method that can be overridden in the derived class. Since derived methods are initialized before the base constructor is invoked, this approach will function correctly. For instance:

class Base {
    protected numThings = 5;

    constructor() {
        console.log('math result: ', this.doMath())
    }

    protected doMath() {
        return 10/this.numThings;
    }
}

class Derived extends Base {
    // Overrides. Would lead to division by 0 in base without overriding doMath
    protected numThings = 0;

    protected doMath() {
        return 100 + this.numThings;
    }
}

// Should display "math result: 100"
const x = new Derived();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Incorporating an else statement into a function that handles AJAX calls upon receiving a response

My code is almost perfect, but there's just one issue. Whenever an invalid email is entered, the else statement in the PHP response makes it look like the form was still successful. How can I modify my current code to display the appropriate error mes ...

What process is involved in implementing TCO (Tail Call Optimization) for a recursive anonymous function in ES5?

Is there a way to optimize an anonymous recursive function for tail call optimization, similar to how a named recursive function can be optimized? If such a method exists, please provide explanations on how to achieve it. Below is an example of a recursi ...

Filtering records by a joined association in Rails using Ajax

My goal is to enable an end user to filter a data grid based on an associated record. The data grid currently displays a list of Activities: The model for activity.rb (the list of records) class Activity < ApplicationRecord belongs_to :student ...

Php/JavaScript Error: Identifier Not Found

I've double-checked my code multiple times, but it still doesn't seem to be working properly. When the PHP runs, the browser console displays the following error: Uncaught SyntaxError: Unexpected identifier. I'm not sure if this is a si ...

Ways to customize decoration in Material UI TextField

Struggling to adjust the default 14px padding-left applied by startAdornment in order to make the adornment occupy half of the TextField. Having trouble styling the startAdornment overall. Attempts to style the div itself have been partially successful, b ...

Updating the MLM binary tree system

In my JavaScript nested object, the IDs of objects increase in depth. By using JavaScript support, I added an object inside ID - 2 with a children array of 3. What I need is to update (increment) the IDs of all siblings in the tree. This code snippet shoul ...

The interactions and functionalities of jQuery sortable and draggable elements

Looking for some assistance with a project involving draggable objects 'field' and 'container', along with a sortable object 'ui-main'. The goal is to drag the two aforementioned objects into 'ui-main', while also al ...

When anchor is set programmatically, the focus outline is not displayed

I am currently facing an issue with my application where I am programmatically setting focus on elements in certain scenarios. While it generally works well, I have noticed that when I set the focus on an anchor element using $("#link1").focus(), the focus ...

Issue: React build script does not support conversion from 'BigInt' to 'number' error

After developing the UI using create-react-app, I encountered an issue. The UI works fine with the normal npm start command, but when I try to build it with npm run build, I get an error saying 'Conversion from 'BigInt' to 'number' ...

Limit input to numbers only in Semantic UI React Form Field

I've developed a custom CurrencyInput React component for users to input numeric values. I set the input type to "number", but unfortunately, this only seems to function properly in Chrome (as Firefox still allows non-numeric entries). Here is the co ...

Obtain additional information to address concerns related to onZoom and onPan issues on the line

Attempting to enhance my Chart.js line chart by fetching more data or utilizing cached backup data during onZoom/onPan events has proven quite challenging. The original code base is too intricate to share entirely, but I will outline the approaches I have ...

Updating the user interface in react-apollo following a delete mutation

After successfully executing a delete mutation in my React Apollo component, the UI of my app did not update as expected. Here is the code snippet for reference: const deleteRoom = async (roomId, client = apolloClient) => { const user = await getUser ...

"Encountering an error: invalid CSRF token in Node.js csurf

I have been utilizing the npm module csurf to generate a token. Initially, I retrieve the token from the server and then utilize it for the /register request. When I replicate these steps in Postman, everything seems to function correctly, but unfortunatel ...

Retrieve the content from paginated pages using ajax, apply a filter to it, and then add it to the existing page

My blog needs more functionality and I want to avoid using paginated pages. My idea was to extract content from these paginated pages through ajax, filter it, and add it to a specific div element. I'm not completely confident if I am on the right tra ...

I possess a certain input and am seeking a new and distinct output

I am looking to insert a word into an <input> and see an altered output. For example, Input = Michael Output = From Michael Jordan function modifyOutput() { var inputWord = document.getElementById("inputField").value; var outputText = "print ...

Having difficulty updating the parent for all PortfolioItem/Feature that were copied for a specific PortfolioItem/MMF

I'm facing a challenge in setting the parent for multiple features that I've copied for a specific MMF. However, only the parent of the last feature is being set. Below is the code snippet responsible for setting the parent: Record represents th ...

The PHP file on the server is missing the mandatory "event" parameter for the EventSource

Explaining this issue was a bit of a challenge. I've set up a Javascript EventSource object with some customized event handlers like so: var source = new EventSource('updates.php'); source.addEventListener('add', addHandler, fals ...

Automatic Clicks in the Chrome Console

I've come across a website that requires me to click on multiple elements scattered throughout the page Here is the code for one of the elements: <span class="icon icon-arrow-2"></span> Is there a way to provide me with the console comm ...

Disable, Hide, or Remove Specific Options in a Single Dropdown Selection

A challenge I am facing involves creating a form with multiple select options that need to be ranked by the user from 1-8. However, I am encountering some difficulties in hiding, removing, or disabling certain select options. Below is an excerpt from my f ...

Modify the CSS properties of the asp:AutoCompleteExtender using JavaScript

Is there a way to dynamically change the CompletionListItemCssClass attribute of an asp:AutoCompleteExtender using JavaScript every time the index of a combobox is changed? Here is the code snippet: ajaxtoolkit: <asp:AutoCompleteExtender ID="autocom" C ...