It’s June 2022 and a new version of ECMAScript has just been released. One of the novelties that this version has brought us are the class fields.

You see, Object-Oriented languages usually have this feature, where you can declare a field on a class in the top-most scope of the class like this:

class Foobar {
  private someNumberField = 10;
  public  aFieldWithNoValue; 
}

Well, aside from the obvious lack of access keywords, this was still impossible in JavaScript. Until ES2022 you usually set those in the constructor (or never mentioned them at all):

class Foobar {
  constructor() {
    this.someNumberField = 10;
  }
}

But because of the proposal1 that has been marinating since 2017, this feature has been brought to life in ES2022, and since then, JS is now like any other OO language.

Hehe.

Well, it has been decided around 2018/2019 that the declaration of class fields will be done in JS using [[Define]] semantics and not [[Set]] semantics.

This means that whenever a class Foobar defines a property (for example, initialized like: someNumberField = 10), JS is

  • not doing this.someNumberField = 10 when creating an instance of the class

but

  • running Object.defineProperty(Foobar, "someProp", 10) somewhere around the class declaration

Why does it matter? Oh boy, it matters a lot. See the heated discussion here.

Most importantly to the TS users, the class fields that were in TypeScript long before ES2022, were originally implemented with [[Set]] semantics. That’s what the TypeScript creators assumed at that time, and I have to admit, [[Set]] semantics make total sense to me.

Sooo, since TS 3.7 there’s this useDefineForClassFields flag that when turned on, flips the implementation to an ECMAScript-compilant one. 2

But the new ECMAScript-compliant behaviour breaks some existing code related to class inheritance and property setters. If you have a derived class that narrows a type of some property (a totally valid thing to do in TS) that is set in the base class with a super() call, it will be set to undefined after the constructor of the derived class returns!

There are a couple of fixes:

  • quick and dirty: set useDefineForClassFields to false if you have it set to true. Unless your target is ES2022 or higher, then you’re left with the other option:

  • fix the code by marking the property in the derived class with the declare keyword. Even if originally you overrode it.

  • or use a transpiler that does provides non-compliant transformation options: https://swc.rs/docs/configuration/compilation#jsctransformusedefineforclassfields


  1. https://github.com/tc39/proposal-class-fields ↩︎

  2. https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#the-usedefineforclassfields-flag-and-the-declare-property-modifier ↩︎