public, private and # fields
In a building made from a blueprint, some doors carry a "Staff only" sign. A visitor sees it and stays out, and the inspector checks at acceptance that no visitor route leads through such doors. But the sign is paper: the door behind it is not locked. A door with a real lock is different. It stays closed even when no inspector is around.
Class fields in TypeScript have both kinds of protection. The private modifier is the sign: only tsc reads it. A field with JavaScript's hash # is the locked door: it stays closed while the program runs, too.
class Account {
owner = "Anna";
private pin = "1234";
check(attempt: string): boolean {
return attempt === this.pin;
}
}
const acc = new Account();
console.log(acc.owner, acc.check("0000")); // Anna falseLet's go through the example line by line:
- The field
owneris declared with no modifier, so it is open to everyone. private pincan be read and changed only inside the classAccount.- The method
checkis part of the class, so the fieldthis.pinis available to it. - Code outside calls
checkbut never sees the PIN itself. The program printsAnna false.
Let's try to read the PIN from outside by adding the line console.log(acc.pin);. tsc does not let it through:
error TS2341: Property 'pin' is private and only accessible within class 'Account'.The message reads: "the property pin is private, and it can be accessed only inside the class Account".
Three access modifiers
An access modifier is written before the name of a field or a method. It says which code may access that class member.
public
The member is open to everyone: to the class's own methods and to code outside. Any field without a modifier behaves this way, so public owner = "Anna" and owner = "Anna" mean the same thing. People write public to show that a field is open on purpose.
private
The member is available only in the body of its own class. Outside it, and even in a subclass, tsc reports error TS2341.
protected
The member is available in the body of its own class and in the classes that extend it with extends. Code outside gets error TS2445: "only accessible within the class and its subclasses".
What tsc leaves of private
Access modifiers are part of the types, and tsc erases types. Let's see what a class with a private field and a # field turns into. Press "Next step" and watch the highlighted lines in both columns.
The word private and the type string are gone, and pin is left an ordinary field.
After compilation:
private pin: stringis left aspin;, an ordinary field like any object's;- the field
#balance = 0went into the JavaScript unchanged; - all the type annotations are gone, as in any other file.
private does not protect at run time
Since pin is an ordinary field in the JavaScript, any code sees it while the program runs:
class Account {
private pin = "1234";
#balance = 50;
}
const acc = new Account();
console.log(Object.keys(acc).join(" ")); // pin
console.log(acc["pin"]); // 1234Object.keys(acc)finds the fieldpin, while the field#balanceis not in the list.acc["pin"]reads the private field.tsclets an access with square brackets and a quoted name through, even to aprivatefield.
A # field stays closed at run time
A field whose name starts with # is closed by JavaScript itself. TypeScript checks it the same way as private, but the protection does not end there:
class Account {
#balance = 50;
show(): number {
return this.#balance;
}
}
const acc = new Account();
console.log(acc.show(), JSON.stringify(acc)); // 50 {}- Inside the class
#balanceis available, soshow()returns50. JSON.stringify(acc)prints{}: the field#balanceis not among the object's properties.
tsc does not let the line acc.#balance through outside the class:
error TS18013: Property '#balance' is not accessible outside class 'Account' because it has a private identifier.And if you run such JavaScript without the check, the program stops with a SyntaxError before its first line. The locked door works without an inspector.
Interviewers often ask how private differs from #. The answer: only tsc checks private, and in the JavaScript the field stays an ordinary property, while a # field stays closed while the program runs. private is enough to keep tsc from letting you make a mistake in the project's code. A # field is needed when no code at all may see the data at run time.
Common mistake
A password was hidden with private, on the assumption that nobody would see it:
class User {
login = "anna";
private password = "secret"; // ✗
}
console.log(JSON.stringify(new User()));tsc finds no errors here, and the program prints the password:
{"login":"anna","password":"secret"}JSON.stringify works at run time and knows nothing about the private sign. This is how a password ends up in a log or in a server's response. Declare the field as #password: JSON.stringify skips it.
Key points
publicopens a class member to everyone (the default),privateonly to its own class,protectedto subclasses as well.- Only
tscchecks access modifiers: in the JavaScript aprivatefield stays an ordinary field. - A
#field stays closed at run time: neither outside code, norObject.keys, norJSON.stringifysees it. - Keep data that must not be shown in a
#field, not in aprivateone.