Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript: field default overwrites value set while executing super constructor

Tags:

oop

typescript

After painful debugging, I've come to conclusion that TypeScript compiler treats my code unfairly. Is that a feature or a bug?

Here's my code:

class BaseClass {
  constructor() {
    this.dynamicMethod();
  }
  dynamicMethod() {
  }
}

class ChildClass extends BaseClass {
  a = 'default';
  constructor() {
    super();
    console.log('@constructor: a =', this.a);
  }
  dynamicMethod() {
    this.a = 'abc';
    console.log("@dynamicMethod a =", this.a);
  }
}

const c = new ChildClass();

When running it, it outputs this:

@dynamicMethod a = abc
@constructor: a = default

The BaseClass constructor nicely calls the overwritten dynamicMethod(), the field a is populated with a correct value, but then that value is blatantly overwritten with the field's default value. I went to check how the compiled code looks like:

// Some infrastructure code was trimmed off
var BaseClass = /** @class */ (function () {
    function BaseClass() {
        this.dynamicMethod();
    }
    BaseClass.prototype.dynamicMethod = function () {
    };
    return BaseClass;
}());
var ChildClass = /** @class */ (function (_super) {
    __extends(ChildClass, _super);
    function ChildClass() {
        var _this = _super.call(this) || this;
        _this.a = 'default';
        console.log('@constructor: a =', _this.a);
        return _this;
    }
    ChildClass.prototype.dynamicMethod = function () {
        this.a = 'abc';
        console.log("@dynamicMethod a =", this.a);
    };
    return ChildClass;
}(BaseClass));
var c = new ChildClass();

The important thing to note here is that the line _this.a = 'default'; follows immediately after the super(), the base class constructor is called. And, the valuable data in field a is overwritten.

Is this how it should be? I tried to fix this by avoiding giving that field the default value. TSLint will then complain with

TS2564: Property 'a' has no initializer and is not definitely assigned in the constructor.

I tried to shut it up by putting something like this.a = 'default'; before the super() call. But that results in this error:

TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.

So, that's pretty sad. If I try to initialize the field before super() call, that is illegal (for obvious reasons), but if it is initialized in the class declaration, then the default value overwrites value that was already populated. Of course, I can move the dynamicMethod() call out from the base class constructor, and call it from the child class constructor. But that triggers quite a lot of changes in all kinds of other places.

like image 506
Passiday Avatar asked Oct 23 '25 14:10

Passiday


1 Answers

I believe that there is nothing wrong here, the super call must be the first call in the child class constructor. So everything works as it should, and this has nothing to do with Typescript in particular.

Flow:

  1. child constructor gets called.
  2. super() is called (sets this to child instance)
  3. parent constructor is executed.
  4. dynamic method is called (with this set to child class) and a is set to abc
  5. child constructor continues to run to completion and sets a to default

You can silence the Typescript compiler by declaring a with the exclamation point public a!:string then the initialization of a is at your discretion.

like image 60
Ivan V. Avatar answered Oct 26 '25 07:10

Ivan V.



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!