Get "PHP 8 in a Nuthshell" (Now with PHP 8.4)
Amit Merchant

Amit Merchant

A blog on PHP, JavaScript, and more

TypeScript's private modifiers Vs. ECMAScript's hash(#) private fields

With the release of TypeScript 3.8, the team has introduced support for the ECMAScript Private Fields into the TypeScript itself. Meaning, you can now declare private fields in the class using # like so.

class Person {
  #name: string;

  constructor(name: string) {
    this.#name = name;
  }

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

By declaring the fields using # makes the field “entirely” private. Meaning, these fields can’t be accessed or even detected outside of the containing class - even by JS users.

So, if we try to access the name property outside of the class like so…

let person = new Person("Jon Doe");
  
person.#name;

…it will throw the following error.

Property ‘#name’ is not accessible outside class ‘Person’ because it has a private identifier.

On the contrary, if the field is declared using TypeScript’s private modifier, it will still throw an error at compile-time, but when TypeScript outputs .js files, it will work fine and will allow access to the private field. Take the following for instance.

class Bar {
  private foo = 10;
}

When we’ll try to access the foo outside the class Bar like so…

console.log(new Bar().foo); // prints '10'

…It will give a compile-time error that “Property ‘foo’ is private and only accessible within class ‘Bar’.” but when TypeScript outputs .js files, it’ll run fine and print 10.

That’s the key difference between Typescript’s private modifiers and ECMAScript’s hash(#) private fields.

That is, the ECMAScript’s hash(#) private fields provide hard privacy which is really useful for strictly ensuring that nobody can make use of any of internal of your classes.

Apart from this, another advantage the ECMAScript’s private fields bring is, these fields can’t be overwritten in the subclass. Take the following for example.

class C {
  #foo = 10;

  cHelper() {
    return this.#foo;
  }
}

class D extends C {
  #foo = 20;

  dHelper() {
    return this.#foo;
  }
}

let instance = new D();
// 'this.#foo' refers to a different field within each class.
console.log(instance.cHelper()); // prints '10'
console.log(instance.dHelper()); // prints '20'

As you can see, even though the subclass D has a similarly named field foo like that of the parent class C, it hadn’t been overridden in D. This wouldn’t be possible if we had declared those with the regular private keyword.

This comes in handy if you’re a library author, removing or renaming a private field should never cause a breaking change.

In closing

So, the bottom line is, if you only want to enforce the privacy at the compile-time/design-time, go with TypeScript’s private modifier. For all the other cases, ECMAScript’s hash(#) private fields can turn out to be a great fit for you.

Like this article?

Buy me a coffee

👋 Hi there! I'm Amit. I write articles about all things web development. You can become a sponsor on my blog to help me continue my writing journey and get your brand in front of thousands of eyes.

Comments?