public, private и поля #
В здании, построенном по чертежу, на некоторых дверях висит табличка «Только для персонала». Посетитель видит её и не заходит, а инспектор при приёмке проверяет, что гостевые маршруты через такие двери не ведут. Но табличка — это бумага: дверь за ней не заперта. Другое дело — дверь с настоящим замком. Она закрыта, даже когда инспектора рядом нет.
У полей класса в TypeScript есть оба вида защиты. Модификатор private — это табличка: её читает только tsc. Поле с решёткой # из JavaScript — запертая дверь: оно закрыто и во время работы программы.
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 falseРазберём пример по строкам:
- Поле
ownerобъявлено без модификатора, поэтому оно открыто всем. private pinможно читать и менять только внутри классаAccount.- Метод
check— часть класса, и ему полеthis.pinдоступно. - Код снаружи вызывает
check, но сам ПИН-код не видит. Программа печатаетAnna false.
Попробуем прочитать ПИН-код снаружи — добавим строку console.log(acc.pin);. tsc её не пропустит:
error TS2341: Property 'pin' is private and only accessible within class 'Account'.Сообщение читается так: «свойство pin закрытое, и обращаться к нему можно только внутри класса Account».
Три модификатора доступа
Модификатор доступа пишут перед именем поля или метода. Он говорит, из какого кода к этому члену класса можно обращаться.
public
Член класса открыт всем: и методам самого класса, и коду снаружи. Так ведёт себя любое поле без модификатора, поэтому public owner = "Anna" и owner = "Anna" означают одно и то же. Слово public пишут, когда хотят показать, что поле открыто намеренно.
private
Член класса доступен только в теле своего класса. Снаружи, и даже в классе-наследнике, tsc сообщит об ошибке TS2341.
protected
Член класса доступен в теле своего класса и в классах, которые расширяют его через extends. Код снаружи получит ошибку TS2445: «доступно только внутри класса и его подклассов».
Что tsc оставит от private
Модификаторы доступа — часть типов, а типы tsc стирает. Посмотрим, во что превратится класс, у которого есть поле private и поле #. Нажимайте «Следующий шаг» и следите за подсвеченными строками в обеих колонках.
Слово private и тип string исчезли, а pin осталось обычным полем.
После компиляции:
- от
private pin: stringосталосьpin;— обычное поле, как у любого объекта; - поле
#balance = 0перешло в JavaScript без изменений; - все аннотации типов исчезли, как в любом другом файле.
При запуске private не защищает
Раз в JavaScript pin — обычное поле, во время работы программы его видит любой код:
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)находит полеpin, а поля#balanceв списке нет.acc["pin"]читает закрытое поле. Обращение через квадратные скобки с именем в кавычкахtscпропускает даже к полюprivate.
Поле # закрыто и при запуске
Поле, имя которого начинается с #, закрыто средствами самого JavaScript. TypeScript проверяет его так же, как private, но защита на этом не кончается:
class Account {
#balance = 50;
show(): number {
return this.#balance;
}
}
const acc = new Account();
console.log(acc.show(), JSON.stringify(acc)); // 50 {}- Внутри класса
#balanceдоступно, поэтомуshow()возвращает50. JSON.stringify(acc)печатает{}: среди свойств объекта поля#balanceнет.
Строку acc.#balance снаружи класса tsc не пропустит:
error TS18013: Property '#balance' is not accessible outside class 'Account' because it has a private identifier.А если запустить такой JavaScript без проверки, программа остановится с SyntaxError ещё до первой строки. Запертая дверь работает и без инспектора.
На собеседовании часто спрашивают, чем private отличается от #. Ответ: private проверяет только tsc, и в JavaScript поле остаётся обычным свойством, а поле # закрыто и во время работы программы. private хватает, чтобы tsc не дал ошибиться в коде проекта. Поле # нужно, когда данные не должен увидеть никакой код при запуске.
Частая ошибка
Пароль спрятали через private и решили, что его никто не увидит:
class User {
login = "anna";
private password = "secret"; // ✗
}
console.log(JSON.stringify(new User()));tsc не найдёт здесь ошибок, а программа напечатает пароль:
{"login":"anna","password":"secret"}JSON.stringify работает при запуске и о табличке private ничего не знает. Так пароль попадёт в лог или в ответ сервера. Объявите поле как #password: его JSON.stringify пропустит.
Главное
publicоткрывает член класса всем (так по умолчанию),private— только своему классу,protected— ещё и подклассам.- Модификаторы доступа проверяет только
tsc: в JavaScript полеprivateостаётся обычным полем. - Поле
#закрыто и при запуске: его не видят ни код снаружи, ниObject.keys, ниJSON.stringify. - Данные, которые нельзя показывать, храните в поле
#, а не вprivate.