TypeScript: Unable to access extended properties in constructor

I’m not sure if this is an intention of typescript or not but acts differently to how I would’ve expected.

If i have a base class and extend it, overriding some base class properties, then want to work with the properties in the child class constructor I still access the parent properties.

This is shown in the below basic example.

class Base {

    public myVar:string = 'Base';

    public constructor() {
        console.log(this.myVar);
    }

}

class Child extends Base {

    public myVar:string = 'Child';

}

var base:Base = new Base(); // 'Base' - As expected
var child:Child = new Child(); // 'Base' - I would've expected this to be 'Child'

console.log(base.myVar); // 'Base' - As expected
console.log(child.myVar); // 'Child' - As expected

This happens in the compiled JS because the super is called before the child properties are set:

function Child() {
    _super.apply(this, arguments);
    this.myVar = 'Child';
}

Is this intended? An issue? Or a trade off because of JS limitations? Thanks!

Note that if I do something like this, it works as I would’ve expected. However it feels like a bit of a hacky work around.

class Base {

    public myVar:string = 'Base';

    public constructor() {
        this.setup();
    }

    protected setup() {
        console.log(this.myVar);
    }

}

class Child extends Base {

    public myVar:string = 'Child';

    public constructor() {
        super();
        this.setup();
    }

}

var base:Base = new Base(); // 'Base'
var child:Child = new Child(); // 'Child' - Now as expected

console.log(base.myVar); // 'Base'
console.log(child.myVar); // 'Child'

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 27 (7 by maintainers)

Most upvoted comments

I would expect that example to give an compile error because you would/should never use this in class variable declaration

I.E.: protected m = this.getX();

Edit: I’ve posted a longer exploration of this at http://stackoverflow.com/questions/43595943/why-are-derived-class-property-values-not-seen-in-the-base-class-constructor/43595944

This is the intended behavior.

The order of initialization is:

  1. The base class initialized properties are initialized
  2. The base class constructor runs
  3. The derived class initialized properties are initialized
  4. The derived class constructor runs

Why is this the order?

First, look closely at the JavaScript emitted for a class. There are only two places where code runs – in the static initialization of the class (where the prototype is set up and static members are initialized), and in the constructor function for the class instance. Property initialization is injected into the first part of the constructor, which means that step 1 and step 2 are necessarily going to happen one after the other.

It’s also clear that the base class constructor has to run before the derived class constructor.

This leaves us with only one alternative:

  1. The derived class initialized properties are initialized
  2. The base class initialized properties are initialized
  3. The base class constructor runs
  4. The derived class constructor runs

That ordering would result in very surprising behavior:

class Base {
    x = 'foo';
    getX() {
        return this.x;
    }
}
class Derived extends Base {
    m = this.getX(); // Would be 'undefined', not 'x'
    constructor() {
        super();
    }
}

I think it’s worth noting that the behaviour of C# is exactly as the alternative mentioned above, i.e.

  1. The derived class initialized properties are initialized
  2. The base class initialized properties are initialized
  3. The base class constructor runs
  4. The derived class constructor runs

The example given that would result in “very surprising” behaviour is prevented by the compiler:

class Base {
    public string x = "foo";
    public string getX() {
        return x;   
    }
}

class Derived : Base {
    public string m = this.getX();   // CS000027 Keyword 'this' is not available in the current context
}

I suspect this is one of the reasons people keep raising this issue - programming in the style they’re used to simply doesn’t work in TS, and you have to use horrible workarounds to accomplish the same thing.

However, the TS declaration style requires being able to access this:

class Test {
    FirstName: string;
    Surname: string;
    FullName = () => `${this.FirstName} ${this.Surname}`;
}

EDIT: As pointed out, the above example would still be fine as it’s a function. So I still think this issue is valid.

They’re going to be really disappointed when ES6 class property initializers have the exact same semantics.

Class property initializers are coming to ES6 classes at some point, and the current proposal has the same behavior as TypeScript’s. This is covered at https://github.com/tc39/proposal-class-public-fields/issues/54 and https://github.com/tc39/proposal-class-public-fields/issues/15

You guys keep talking about this like we had three options:

  1. Do the right thing
  2. Do a weird thing where derived properties are set before base constructors run
  3. Do a weird thing where derived properties are set after base constructors run

The first option does not exist. The first option does not exist.

the person implementing this didn’t understand the difference between class definitions and object (or JS “function”) definitions.

For me it shows that whoever implemented class property initialisers in TypeScript doesn’t really grasp the difference between setting properties in the object instance or having them inherited and initialised from a child class.

Seriously?

Adding to PMXScott their argument is that typescript usually perceived as a superset of ES6 which has a typical inheritance system.

The problem that comes forth from this inheritance that typescript implements is one where when a framework wants to have a class initialize some part of the system which also needs to be configurable on a class by class basis it will simply be ignored by the language. Thus using inheritance in these kinds of situations will demand a pretty weird structure.

I.E. Backbonejs is using an initialize function for its views. When one is extending from this view and wants to for example overwrite some auto rendering function (like implemented by the Chapin library) this will not be overwritten until after the initialization is complete.

Circumventing this, for now, is still possible by using the constructor of the class and passing them in an acrobatic way on as options to the base view. But with the support of ES6 classes will be less and less supported want IMHO will not encourage the use of typescript by instead will enforce developers to move away.

One might argue that typescript is meant for the smaller projects, but this, in my opinion, is not the case since type casing I the entire idea of the language and there for meant for larger project ergo for projects using frameworks.

These kinds of mistakes make me wonder if typescript will be a lasting language.

Edit: miss read mhegazy’s comment

@paishin @RyanCavanaugh @mhegazy I’m not sure what you guys are talking about, because ES6 doesn’t even support proper class property initialisers. It requires you to set the object instance properties in the constructor just like ES5.

So how can you say “that’s just how ES6 works” or “ES6 will have the same semantics” if the whole notion of class property initialisers doesn’t even exist in the latest ES8 draft?

Since TypeScript is emulating class property initialisers, it should do so in a way that we would expect ES9 to maybe implement them, or at least implement it the way it’s implemented in just about any other OOP language, since those are what TypeScript is basing this language feature on in the first place. You can’t use the way ES6 does object property initialisation as an excuse to just break a separate language feature.

For me it shows that whoever implemented class property initialisers in TypeScript doesn’t really grasp the difference between setting properties in the object instance or having them inherited and initialised from a child class.

It’s just plain incorrect from an OOP standpoint.

When setting properties from the constructor, you already have an object instance of a class. You’re working with that instance and what class it was doesn’t really matter anymore, because the class was only used as a template for the object instance.

When instantiating a class that extends another class (new Child()), you’re expecting the object instance to be based on the complete flattened “template” of the class you’re instantiating, including class property initialisers. By the time you’re in the context of the object’s constructor, you’re already working with the object, which should be based on the class you instantiated, which was Child and not Base. So why are you getting Base’s properties? It just doesn’t make sense.

This makes no sense what so ever in my view.

JS is prototypal. The object oriented community found it weird and came up with loads of hacks to simulate class based inheritance. In doing so there became lots of gotchas such as accidentally forgetting the “new” keyword, which in turn created more hacks. The JS comity finally decided to support the vast number of object oriented users of JS by adding in class support. However, they decided they didn’t like being pressured into adding “normal” class based inheritance that most people would instantly understand (and assume) and so decided to follow the somewhat normal JS way of doing the occasional “wtf” implementation to trip up users ( https://martin-thoma.com/javascript-wtf/ https://www.smashingmagazine.com/2011/05/10-oddities-and-secrets-about-javascript/ http://charlieharvey.org.uk/page/javascript_the_weird_parts to name a few). Implementing sub class constructors in a way that most of the object oriented community will trip up on I guess is at least sticking with the core of the JS craziness! Shame though that TypeScript still follows that philosophy when it has so much of everything else right (in my opinion).

I just refer to non-downleveled classes (i.e. classes that are run natively) as “ES6 classes” because ES6 is the spec version that added them.

I actually misspoke earlier, because in ES6 it’s illegal for the derived class to do anything before calling super. So there is literally no other option than the one we chose.

Any further discussion should be at https://github.com/tc39/proposal-class-public-fields/issues/54 where the official ES spec for this behavior is being worked out.

Seriously?

I don’t think he quite meant it like that @RyanCavanaugh . It’s clear that you and the rest of the TypeScript team do understand it very well (seriously, you guys have made a compiler!) and a decision had to be made.

It’s easy for someone who has a strong belief to develop tunnel vision on a problem and forget that the problem is in fact quite small given the bigger picture. This problem is hardly a show stopper and once people understand it, they can work with it (though you wouldn’t believe it given how much effort we have all contributed here).

We are an inquisitive bunch and like to know why 😄 You won’t ever please everyone and you won’t necessarily convince everyone. Just how it is. Don’t take any of this personally 😃