JavaScript private class fields and the TypeScript private modifier

In this post we’ll shed some light on JavaScript private class fields and see how they compare to the TypeScript private modifier.

JavaScript private class fields and the TypeScript private modifier

JavaScript private class fields and the need for privacy

Historically JavaScript had no native mechanism for protecting variables from access, unless of course, the quintessential closure.

Closures are the foundation for a lot of private-like patterns in JavaScript like the popular module pattern. But after ECMAScript 2015 classes took over in recent years, developers felt the need for more control over classes member privacy.

The class field proposal (at the time of writing in stage 3) tries to solve the problem with the introduction of private class fields.

Let’s see how they look.

JavaScript private class fields, an example

Here’s a JavaScript class with private fields, note that unlike “public” members every private field must be declared before access:

class Person {
  #age;
  #name;
  #surname;

  constructor(name, surname, age) {
    this.#name = name;
    this.#surname = surname;
    this.#age = age;
  }

  getFullName() {
    return `${this.#name} + ${this.#surname}`;
  }
}

Private class fields are not accessible from outside the class:

class Person {
  #age;
  #name;
  #surname;

  constructor(name, surname, age) {
    this.#name = name;
    this.#surname = surname;
    this.#age = age;
  }

  getFullName() {
    return `${this.#name} + ${this.#surname}`;
  }
}

const marta = new Person("Marta", "Cantrell", 33);
console.log(marta.#age); // SyntaxError

This is true “privacy”. At this point if you now a bit of TypeScript you might ask what “native” private fields have in common with the private modifier in TypeScript.

Well, the answer is: nothing. But why?

The private modifier in TypeScript

The private modifier in TypeScript should be familiar to developers coming from more traditional backgrounds. In brief, the keyword is meant to deny class members access from outside the class.

But let’s not forget, TypeScript is a layer on top of JavaScript and the TypeScript compiler is supposed to strip away any fancy TypeScript annotation, including private.

That means the following class doesn’t do what you think it does:

class Person {
  private age: number;
  private name: string;
  private surname: string;

  constructor(name: string, surname: string, age: number) {
    this.name = name;
    this.surname = surname;
    this.age = age;
  }

  getFullName() {
    return `${this.name} + ${this.surname}`;
  }
}

const liz = new Person("Liz", "Cantrill", 31);
// @ts-ignore
console.log(liz.age);

Without // @ts-ignore, accessing liz.age throws an error only in TypeScript, yet after the compilation you end up with the following JavaScript code:

"use strict";
var Person = /** @class */ (function () {
    function Person(name, surname, age) {
        this.name = name;
        this.surname = surname;
        this.age = age;
    }
    Person.prototype.getFullName = function () {
        return this.name + " + " + this.surname;
    };
    return Person;
}());

var liz = new Person("Liz", "Cantrill", 31);
console.log(liz.age); // 31

As expected we’re free to print liz.age. The main take here is that private in TypeScript is not so private, and it feels convenient only at the TypeScript level, not for “real privacy”.

And now let’s get to the point: “native” private class fields in TypeScript.

Private class fields in TypeScript

TypeScript 3.8 will support ECMAScript private fields, not to be confused with the TypeScript private modifier.

Here’s a class with private class fields in TypeScript:

class Person {
    #age: number;
    #name: string;
    #surname: string;

    constructor(name:string, surname:string, age:number) {
        this.#name = name;
        this.#surname = surname;
        this.#age = age;
    }

    getFullName() {
        return `${this.#name} + ${this.#surname}`;
    }
}

Not so different from vanilla JavaScript, besides type annotations. Members cannot be accessed from the outside. But the real problem with private fields in TypeScript is that they use WeakMap under the hood.

To compile this code we need to adjust the target compilation version in tsconfig.json, which must be at least ECMAScript 2015:

{
  "compilerOptions": {
    "target": "es2015",
    "strict": true,
    "lib": ["dom","es2015"]
  }
}

This could be a problem depending on the target browser, unless you intend to ship a polyfill for WeakMap, which at that point becomes too much work if it’s just for the sake of writing fancy new syntax.

There is always this tension in JavaScript, where you really want to use the new syntax, but on the other hand you don’t want to let the UX down with a gazillion polyfills.

On the flip side I don’t think you should worry too much about private class fields, even if you want to ship to newer browsers. At least for now. The support for private fields is almost non-existent. Not even Firefox has implemented the proposal.

Conclusions

Still a proposal at the time of writing, JavaScript class fields are interesting, but the support among browser vendors is poor. What’s your take on this feature?

Here’s mine:

  • I like ES private class fields (though I dislike the #)
  • I never relied to much on private in TypeScript, useful only at the TS level
  • I’ll wait until private class fields land in browsers
  • I wouldn’t use private class fields in TS today

To learn more about TypeScript classes check this out.

The official announcement for TypeScript 3.8 private fields.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.