typescriptoopconstructorinitialization

initializing inline and initializing inside constructor which does run first in Typescript?


I'm learning about class concept in typescript. I recognize that there are two ways to initialize fields (properties) in typescript: initializing inline the class body and initializing inside the constructor method. I wonder that initializing inline and initializing inside the constructor which is run before another.

I tried the following code to test but I still don't know which runs first.

class Point{
    x :number = 0;
    y: number = 0;
    constructor();
    constructor(x:number,y?:number) ;
    constructor(x?:number,y?:number) {
        if(typeof x =='number') this.x = x;
        if(typeof y =='number') this.y = y
    }
}


let p = new Point(1);
console.log(`${p.x} ${p.y}`);

the result of the code is: 1 0 I have googled a lot and read some other questions ( Initializing variables inline during declaration vs in the constructor in Angular with TS) but I have not find the answer.

Thanks for considering my stupid question.


Solution

  • Class fields and their behavior are part of JavaScript (as of ES2019), and TypeScript should generally downlevel to behave the same even if you --target an earlier version. (There is a wrinkle there, since TypeScript introduced class fields before they existed in JavaScript, and the JavaScript implementation differed somewhat from TypeScript's assumptions, leading to the --useDefineForClassFields compiler flag, but that doesn't much change the behavior described herein). According to the documentation: "Public instance fields are added to the instance either at construction time in the base class (before the constructor body runs), or just after super() returns in a subclass."


    So, generally speaking: initializers on the class field declaration happen before assignments inside the constructor body.

    class Test {
        x: number = (console.log("field declaration"), 1)
        constructor() {
            this.x = (console.log("constructor body"), 2);
        }
    }
    const t = new Test();
    //[LOG]: "field declaration" 
    //[LOG]: "constructor body" 
    
    console.log(t.x)
    //[LOG]: 2 
    

    Here I'm using the comma operator (,) to log to the console shortly before each assignment. You can see that the field declaration initializer happens before the constructor assignment.


    When you make subclasses, superclass stuff all happens first, and then subclass stuff, but in both cases the field declaration happens before the rest of the constructor body:

    class SubTest extends Test {
        x: number = (console.log("field declaration subclass"), 3);
        constructor() {
            super();
            this.x = (console.log("constructor body subclass"), 4)
        }
    }
    
    const s = new SubTest();
    // [LOG]: "field declaration" 
    // [LOG]: "constructor body" 
    // [LOG]: "field declaration subclass" 
    // [LOG]: "constructor body subclass" 
    
    console.log(s.x)
    // [LOG]: 4
    

    This might look like an exception, in that the superclass constructor runs before the subclass field declaration, and indeed, if you could assign a property in the subclass constructor body before calling super() then it would be an exception. But that's prohibited; this basically doesn't exist before super() is called:

    class Whoops extends Test {
        constructor() {
            this.x = 0; // error!
            //~~ <-- 'super' must be called before accessing 
            //this' in the constructor of a derived class.
            super();
        }
    }
    new Whoops() // error at runtime, "must call super constructor"
    

    So: field declaration initializers happen before assignments in the constructor body of the same class.

    Playground link to code