A (somewhat) deep dive into TypeScript constructor intricacies, step-by-step
Tags: typescript, javascript, deepdive, tip
Published: 2024-03-05 18:17:45

If you've ever wondered how TypeScript's constructor shenanigans work, here's a quick one for you.

**You have a TL;DR at the bottom with the key takeaways if you're in a rush!**

---

Unfortunately the TypeScript docs don't really go into any detail at all of how it translates class property initialisers, the constructor shorthand and explicit assignments into JavaScript and how all of them play together, so I took it upon myself to find out.

## How does it transpile?

First of all, let's see the result of the simplest classes and constructors:

```ts
class InitialiserOnly {
    a: number = 1;
}

class ConstructorShorthand {
    constructor(public a: number = 1) { }
}

class ManualAssignment {
    a: number;
    
    constructor(a: number = 1) {
        this.a = a;
    }
}

class ManualAssignment2 {
    a: number;
    
    constructor() {
        this.a = 1;
    }
}
```

Transpiles to:


```js
"use strict";
class InitialiserOnly {
    constructor() {
        this.a = 1;
    }
}
class ConstructorShorthand {
    constructor(a = 1) {
        this.a = a;
    }
}
class ManualAssignment {
    constructor(a = 1) {
        this.a = a;
    }
}
class ManualAssignment2 {
    constructor() {
        this.a = 1;
    }
}
```

As you can see, everything pretty much becomes the same thing: assignments inside the constructor, thus at runtime they'll all be equivalent (`a === 1`). Fairly straightforward.

Then what is the difference? Let's see

## What about multiple operations on the same variable?

```ts
class ConstructorFun {
    a: number = 2;

    constructor(value: number = 3) {
        this.a = value;
    }
}
```

Transpiles to:

```js
"use strict";
class ConstructorFun {
    constructor(value = 3) {
        this.a = 2;
        this.a = value;
    }
}
```

Interesting and unsurprising... `a = 3` in this case, as the last operation is the explicit `this.a` assignment, which results in the value of `a` being the parameter passed to the constructor.

### What if we used the constructor shorthand instead?

```ts
class ConstructorFun2 {
    a: number = 2;

    constructor(public a: number = 3) { }
}
```

Transpiles to:


```js
"use strict";
class ConstructorFun2 {
    constructor(a = 3) {
        this.a = a;
        this.a = 2;
    }
}
```

So completely the opposite this time around: `a = 2`. Note that in this case TypeScript complains about a duplicated identifier (rightfully so).

Funnily enough, this also applies to the access modifiers (`public/protected/private`), meaning this:


```ts
class ConstructorFun3 {
    protected a: number = 2;

    constructor(public a: number = 3) { }
}
```
Results in `a` being `protected`, not `public`. The class initialiser trumps the shorthand again.


## How does this matter to me? (TL;DR)

Enough blabbering, here's the takeaways.

### Order of _execution_
1. The **constructor shorthand** assignments (`constructor(readonly b = 3) {...}`)
2. The **initialisers** (`private b: string = "test" //...`)
3. The **manual/explicit assignments** inside the constructor (`this.[prop] = value`)

_Note that all assignments from the first point happen before the first from the next one._

### Order of _importance_

The opposite way around (explicit assignment > initialiser > shorthand).

### Performance

In terms of performance, they all should perform equally well, as the JavaScript they transpile to is the same (operations inside the constructor), the only difference being in the order of operations.


## Source code

Go ahead and play yourself! [Playground Link](https://www.typescriptlang.org/play?ts=5.3.3#code/MYGwhgzhAECSB2BLALosJEQKYCcDy8IAntAN4BQ0V0YAXNPAK4C2ARrtALzQCMA3OQC+5cqEgwAwgHt4EZDkbBkUnAGUAFiuTqw8ACZlK1YDLkKlKgBQAHRqwzAa9Jmw7ceASjLRhw0eChoAFldRnQAQShEAHN4Zix4ZENqJwYWdhwBIyoTWXlFZRxLOjTXHC5eLwoUlO1MADowCrABFN8RMUCQpgio2PjEgCZk6hKXDKyU3LMCqyrsmrqIRor+BfbyAHpN6ABafYPDw46AyVN8ixwAMUZ4Eaox9LdoQcnjc-NCywA3dEYsZxPcrcADM8xq1CWK24vxA-1a1A2nTOeU+Khu8GG1VGgLKFVeIimH1mRVs9kQjkeeNBVR8QhO4mg0lRJIxIPu0GsOCkyCwSiwBipGXxbxyxMuNjsDlS42eYO8SNMUhAWHqICk0Us8CwAHcmeLChjLDwAAweDx8aDbXgm0RKlVqjVa3X6lmXDGDY1mi1WnaDLY7abK1XqzXavXMmbu24gr0eRo+60AEUY1gcYF50EQegSqAAZohcAAaTnc3n8vQiETWo61g4MwIAUW+uCIdXg0XCyAIwCwHNY9DMiA7FQARNhcnpRwicoP5MPomO6jgp4T3m6vmTpU1uKOCzg5KPwRDoFDHLu81JGDhtNP1vS7bJg46wy7m63253u-Be5ZzVkgA)

---

## Extra: What about combinations with multiple variables?

Well, let's play around one final bit:

```ts
class EverythingAtOnce {
    b: string = "second";
    c: string = "third"

    constructor(public a = "first") {
        this.c = "fourth";
    }
}

console.log(new EverythingAtOnce());
```

Transpiles to exactly what you would expect:

```js
"use strict";
class EverythingAtOnce {
    constructor(a = "first") {
        this.a = a;
        this.b = "second";
        this.c = "third";
        this.c = "fourth";
    }
}
console.log(new EverythingAtOnce());
```
There you go, hope this was as useful to you as it was for me!