Understanding the Private Keyword
Exploring the concept of the private keyword in TypeScript reveals its role as a compile-time annotation. Specifically, it indicates that a property is restricted to access within a specific class:
class PrivateKeywordClass {
private value = 1;
}
const obj = new PrivateKeywordClass();
obj.value // Results in compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.
Despite the compile-time enforcement, bypassing this restriction is feasible by manipulating the type information:
const obj = new PrivateKeywordClass();
(obj as any).value // No compile error detected
It's important to note that the private
keyword lacks runtime enforcement.
Examining the Emitted JavaScript
During the TypeScript-to-JavaScript compilation process, the private
keyword is essentially discarded:
class PrivateKeywordClass {
private value = 1;
}
Transformed into:
class PrivateKeywordClass {
constructor() {
this.value = 1;
}
}
This transformation demonstrates why the private
keyword fails to provide runtime safeguarding – in the resulting JavaScript code, it simply becomes a standard property.
Introducing Private Fields
Unlike the private fields, which ensure property privacy at runtime:
class PrivateFieldClass {
#value = 1;
getValue() { return this.#value; }
}
const obj = new PrivateFieldClass();
// Accessing '#value' outside the class context leads to unavailability
obj.value === undefined // Incorrect field reference
obj.getValue() === 1 // However, the class itself can access the private field!
// Attempting to access a private field from outside a class triggers a runtime syntax error:
obj.#value
// Accessing private fields of another class leads to a runtime type error
class Other {
#value;
getValue(obj) {
return obj.#value // Error: Read of private field #value from an object which did not contain the field
}
}
new Other().getValue(new PrivateKeywordClass());
When utilizing private fields in TypeScript and targeting ES2021 or older JavaScript versions, TypeScript synthesizes code to mimic private field behavior at runtime using a WeakMap
.
class PrivateFieldClass {
constructor() {
_x.set(this, 1);
}
}
_x = new WeakMap();
For ES2021 and newer targets, TypeScript emits the private field directly:
class PrivateFieldClass {
constructor() {
this.#x = 1;
}
#x;
}
Choosing Between Private Keyword and Private Fields
The decision between the private
keyword and private fields hinges on the specific requirements of your project.
The private
keyword serves as a reliable default option, fulfilling its intended purpose effectively and exhibiting a track record of successful usage within the TypeScript community. Switching an entire codebase to adopt private fields may not be necessary, especially if the target is not esnext
, as private fields in TypeScript emit JavaScript code that could impact performance. Additionally, nuances distinguish private fields from the private
keyword.
However, if runtime privacy enforcement or outputting esnext
JavaScript is essential, opting for private fields is recommended.
Moreover, evolving community norms and conventions regarding the usage of private fields versus the private
keyword highlight the dynamic nature of these features within the JavaScript/TypeScript ecosystems.
Key Distinctions to Note
Private fields evade detection by methods like Object.getOwnPropertyNames
and are not serialized by JSON.stringify
Inheritance intricacies exist, with TypeScript prohibiting the declaration of a private property in a subclass sharing the name with a private property in the superclass for the private
keyword but not for private fields
A private
keyword private property with no initializer does not result in a property declaration in the compiled JavaScript, unlike private fields
Further Resources: