Wenbo
Wenbo

Reputation: 1452

Property initialisation order Typescript

I have 2 classes, A and B in Typescript. B derives from A, A's constructor calls function init and B overrides init. a.ts

export default class A {
    constructor() {
        this.init();
    }

    init() {
        console.log("A.init()")
    }
}

b.ts

import A from "./a"

export default class B extends A {
    public bx = 3;
    public by = 10;
    public bz = 0;
    constructor() {
        super();
    }

    init(){
        this.bx = 5;
        this.bz = this.by;
        console.log("B.init()")
    }
}

The js files compiled by tsc were like

a.js

export default class A {
    constructor() {
        this.init();
    }
    init() {
        console.log("A.init()");
    }
}

b.js

import A from "./a";
export default class B extends A {
    constructor() {
        super();
        this.bx = 3;
        this.by = 10;
        this.bz = 0; //even worse
    }
    init() {
        this.bx = 5;
        this.bz = this.by;
        console.log("B.init()");
    }
}

No surpise in a.js. But in b.js, we can see a line this.bx = 3; after super() in the constructor. While I agree it is necessary to initialise a property like this.bx = 3; which is a declaration in the class, I simply cannot understand why in this case, it has this.bx=3; after super() even theoretically the compiler should be able to know this.bx has been set and is not uninitialised from the AST. What's the consideration? It should only init a property if the property has never been assigned. In my case, I suppose it should be

import A from "./a";
export default class B extends A {
    constructor() {
        super();
    }
    init() {
        this.bx = 3; // initialisation from the declaration can appear here before the first time use. It can be optimized because of the next line assignment
        this.bx = 5; // first time use `this.bx`
        this.by = 10; // the initialisation here is because this.by is used in the next line
        this.bz = this.by; 
        console.log("B.init()");
    }
}

Compare to c#

using System;
                    
public class Program
{
    public static void Main()
    {
        B b = new B();
        Console.Out.Write("x: " + b.x + " y: " + b.y);
    }
}

class A {
    public A() {
        init();
    }
    
    public virtual void init() {
    }
}

class B : A {
    public int x = 3;
    public int y = 10;
    public int z = 4;
    public B() : base() {
    }
    
    public override void init() {
        this.x = 5;
        this.y = this.z;
    }
}

You will get x: 5 y: 4.

Upvotes: 0

Views: 562

Answers (1)

spender
spender

Reputation: 120480

There are very sound reasons to ensure that your base class is fully initialized before initialization of the extended sub class happens, and this is mandated in the MDN documentation for super:

When used in a constructor, the super keyword appears alone and must be used before the this keyword is used

By having your base class call an overridden method in its constructor that fiddles with properties of this of the subclass, you've messed up the order of initialization. By violating this important requirement, any assumptions that compilers make on the back of this requirement are now broken.

Mayhem follows...

The most preferable outcome here would be a fat compilation error.

Upvotes: 1

Related Questions