In TypeScript 3.8+, what are the differences between using the private keyword to mark a member private:
class PrivateKeywordClass { private value = 1; } And using the # private fields proposed for JavaScript:
class PrivateFieldClass { #value = 1; } Should I prefer one over the other?
Private methods/members are accessible only from inside the class. Protected methods/members are accessible from inside the class and extending class as well.
TypeScript includes the keywords public, protected, and private to control access to the members of a class such as properties or methods. Public class members are visible from within and outside the class, protected are visible form the class and its descendants, and private are visible from within the class only.
Private fields are accessible on the class constructor from inside the class declaration itself. They are used for declaration of field names as well as for accessing a field's value. It is a syntax error to refer to # names from out of scope.
private. jsprivate is not a reserved keyword, I can set it to anything. Luckily, there are a couple of workarounds you can use to implement private properties and methods in your JavaScript code.
The private keyword in TypeScript is a compile time annotation. It tells the compiler that a property should only be accessible inside that class:
class PrivateKeywordClass { private value = 1; } const obj = new PrivateKeywordClass(); obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'. However compile time checking can be easily bypassed, for example by casting away the type information:
const obj = new PrivateKeywordClass(); (obj as any).value // no compile error The privatekeyword is also not enforced at runtime
When compiling TypeScript to JavaScript, the private keyword is simply removed:
class PrivateKeywordClass { private value = 1; } Becomes:
class PrivateKeywordClass { constructor() { this.value = 1; } } From this, you can see why the private keyword does not offer any runtime protection: in the generated JavaScript it's just a normal JavaScript property.
Private fields ensure that properties are kept private at runtime:
class PrivateFieldClass { #value = 1; getValue() { return this.#value; } } const obj = new PrivateFieldClass(); // You can't access '#value' outside of class like this obj.value === undefined // This is not the field you are looking for. obj.getValue() === 1 // But the class itself can access the private field! // Meanwhile, using a private field outside a class is a runtime syntax error: obj.#value // While trying to access the private fields of another class is // a runtime type error: class Other { #value; getValue(obj) { return obj.#value // TypeError: Read of private field #value from an object which did not contain the field } } new Other().getValue(new PrivateKeywordClass()); TypeScript will also output a compile time error if you try using a private field outside of a class:

Private fields come from a JavaScript proposal and also work in normal JavaScript.
If you use private fields in TypeScript and are targeting older versions of JavaScript for your output, such as es6 or es2018, TypeScript will try to generate code that emulates the runtime behavior of private fields
class PrivateFieldClass { constructor() { _x.set(this, 1); } } _x = new WeakMap(); If you are targeting esnext, TypeScript will emit the private field:
class PrivateFieldClass { constructor() { this.#x = 1; } #x; } It depends on what you are trying to achieve.
The private keyword is a fine default. It accomplishes what it was designed to accomplish and has been used successfully by TypeScript developers for years. And if you have an existing codebase, you do not need to switch all of your code to use private fields. This is especially true if you are not targeting esnext, as the JS that TS emits for private fields may have a performance impact. Also keep in mind that private fields have other subtle but important differences from the private keyword
However if you need to enforce runtime privateness or are outputting esnext JavaScript, than you should use private fields.
Also keep in mind that organization/community conventions on using one or the other will also evolve as private fields become more widespread within the JavaScript/TypeScript ecosystems
Private fields are not returned by Object.getOwnPropertyNames and similar methods
Private fields are not serialized by JSON.stringify
There are importance edge cases around inheritance.
TypeScript for example forbids declaring a private property in a subclass with the same name as a private property in the superclass.
class Base { private value = 1; } class Sub extends Base { private value = 2; // Compile error: } This is not true with private fields:
class Base { #value = 1; } class Sub extends Base { #value = 2; // Not an error } A private keyword private property without an initializer will not generate a property declaration in the emitted JavaScript:
class PrivateKeywordClass { private value?: string; getValue() { return this.value; } } Compiles to:
class PrivateKeywordClass { getValue() { return this.value; } } Whereas private fields always generate a property declaration:
class PrivateKeywordClass { #value?: string; getValue() { return this.#value; } } Compiles to (when targetting esnext):
class PrivateKeywordClass { #value; getValue() { return this.#value; } } #-private fieldsPreface:
#-private, hard private, run-time private#-private fields provide compile-time and run-time privacy, which is not "hackable". It is a mechanism to prevent access to a member from outside the class body in any direct way.
class A { #a: number; constructor(a: number) { this.#a = a; } } let foo: A = new A(42); foo.#a; // error, not allowed outside class bodies (foo as any).#bar; // still nope. #-private fields get a unique scope. Class hierarchies can be implemented without accidental overwrites of private properties with equal names.
class A { #a = "a"; fnA() { return this.#a; } } class B extends A { #a = "b"; fnB() { return this.#a; } } const b = new B(); b.fnA(); // returns "a" ; unique property #a in A is still retained b.fnB(); // returns "b" TS compiler fortunately emits an error, when private properties are in danger of being overwriten (see this example). But due to the nature of a compile-time feature everything is still possible at run-time, given compile errors are ignored and/or emitted JS code utilized.
Library authors can refactor #-private identifiers without causing a breaking change for clients. Library users on the other side are protected from accessing internal fields.
#-private fieldsBuilt-in JS functions and methods ignore #-private fields. This can result in a more predictable property selection at run-time. Examples: Object.keys, Object.entries, JSON.stringify, for..in loop and others (code sample; see also Matt Bierner's answer):
class Foo { #bar = 42; baz = "huhu"; } Object.keys(new Foo()); // [ "baz" ] private keywordPreface:
private keyword in TS docs private members of a class are conventional properties at run-time. We can use this flexibility to access class internal API or state from the outside. In order to satisfy compiler checks, mechanisms like type assertions, dynamic property access or @ts-ignore may be used amongst others.
Example with type assertion (as / <>) and any typed variable assignment:
class A { constructor(private a: number) { } } const a = new A(10); a.a; // TS compile error (a as any).a; // works const casted: any = a; casted.a // works TS even allows dynamic property access of a private member with an escape-hatch:
class C { private foo = 10; } const res = new C()["foo"]; // 10, res has type number Where can private access make sense? (1) unit tests, (2) debugging/logging situations or (3) other advanced case scenarios with project-internal classes (open-ended list).
Access to internal variables is a bit contradictory - otherwise you wouldn't have made them private in the first place. To give an example, unit tests are supposed to be black/grey boxes with private fields hidden as implementation detail. In practice though, there may be valid approaches from case to case.
TS private modifiers can be used with all ES targets. #-private fields are only available for target ES2015/ES6 or higher. In ES6+, WeakMap is used internally as downlevel implementation (see here). Native #-private fields currently require target esnext.
Teams might use coding guidelines and linter rules to enforce the usage of private as the only access modifier. This restriction can help with consistency and avoid confusion with the #-private field notation in a backwards-compatible manner.
If required, parameter properties (constructor assignment shorthand) are a show stopper. They can only be used with private keyword and there are no plans yet to implement them for #-private fields.
private might provide better run-time performance in some down-leveling cases (see here).private keyword notation better 😊.Both approaches create some kind of nominal or branded type at compile-time.
class A1 { private a = 0; } class A2 { private a = 42; } const a: A1 = new A2(); // error: "separate declarations of a private property 'a'" // same with hard private fields Also, both allow cross-instance access: an instance of class A can access private members of other A instances:
class A { private a = 0; method(arg: A) { console.log(arg.a); // works } } Sources
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With