Перейти к содержимому
Теория · Классы · урок 3 из 12

public, private и поля #

В здании, построенном по чертежу, на некоторых дверях висит табличка «Только для персонала». Посетитель видит её и не заходит, а инспектор при приёмке проверяет, что гостевые маршруты через такие двери не ведут. Но табличка — это бумага: дверь за ней не заперта. Другое дело — дверь с настоящим замком. Она закрыта, даже когда инспектора рядом нет.

У полей класса в TypeScript есть оба вида защиты. Модификатор private — это табличка: её читает только tsc. Поле с решёткой # из JavaScript — запертая дверь: оно закрыто и во время работы программы.

TypeScript
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

Разберём пример по строкам:

  1. Поле owner объявлено без модификатора, поэтому оно открыто всем.
  2. private pin можно читать и менять только внутри класса Account.
  3. Метод check — часть класса, и ему поле this.pin доступно.
  4. Код снаружи вызывает check, но сам ПИН-код не видит. Программа печатает Anna false.

Попробуем прочитать ПИН-код снаружи — добавим строку console.log(acc.pin);. tsc её не пропустит:

TEXT
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 и поле #. Нажимайте «Следующий шаг» и следите за подсвеченными строками в обеих колонках.

ВживуюЧто tsc оставит от private и #шаг 1 из 7

Слово private и тип string исчезли, а pin осталось обычным полем.

Подсветка показывает строку TypeScript и строку JavaScript, которая из неё получилась

После компиляции:

  • от private pin: string осталось pin; — обычное поле, как у любого объекта;
  • поле #balance = 0 перешло в JavaScript без изменений;
  • все аннотации типов исчезли, как в любом другом файле.

При запуске private не защищает

Раз в JavaScript pin — обычное поле, во время работы программы его видит любой код:

TypeScript
class Account {
    private pin = "1234";
    #balance = 50;
}

const acc = new Account();
console.log(Object.keys(acc).join(" ")); // pin
console.log(acc["pin"]); // 1234
  1. Object.keys(acc) находит поле pin, а поля #balance в списке нет.
  2. acc["pin"] читает закрытое поле. Обращение через квадратные скобки с именем в кавычках tsc пропускает даже к полю private.

Поле # закрыто и при запуске

Поле, имя которого начинается с #, закрыто средствами самого JavaScript. TypeScript проверяет его так же, как private, но защита на этом не кончается:

TypeScript
class Account {
    #balance = 50;

    show(): number {
        return this.#balance;
    }
}

const acc = new Account();
console.log(acc.show(), JSON.stringify(acc)); // 50 {}
  1. Внутри класса #balance доступно, поэтому show() возвращает 50.
  2. JSON.stringify(acc) печатает {}: среди свойств объекта поля #balance нет.

Строку acc.#balance снаружи класса tsc не пропустит:

TEXT
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 и решили, что его никто не увидит:

TypeScript
class User {
    login = "anna";
    private password = "secret"; // ✗
}

console.log(JSON.stringify(new User()));

tsc не найдёт здесь ошибок, а программа напечатает пароль:

TEXT
{"login":"anna","password":"secret"}

JSON.stringify работает при запуске и о табличке private ничего не знает. Так пароль попадёт в лог или в ответ сервера. Объявите поле как #password: его JSON.stringify пропустит.

Главное

  • public открывает член класса всем (так по умолчанию), private — только своему классу, protected — ещё и подклассам.
  • Модификаторы доступа проверяет только tsc: в JavaScript поле private остаётся обычным полем.
  • Поле # закрыто и при запуске: его не видят ни код снаружи, ни Object.keys, ни JSON.stringify.
  • Данные, которые нельзя показывать, храните в поле #, а не в private.