Menu

Модификаторы доступа TypeScript: public, private, protected

В TypeScript три модификатора доступа, public, private и protected, плюс readonly. Что разрешает каждый, почему private в TypeScript это проверка времени компиляции, а приватные поля JavaScript #private соблюдаются во время выполнения, и что выбрать.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

В TypeScript три модификатора доступа для членов класса: public (по умолчанию), protected и private. Они определяют, где можно использовать член, и компилятор сообщает о любом обращении не из того места.

Последняя строка это ошибка компиляции, но посмотрите, что она напечатала, когда её всё-таки запустили: 123-45-6789. Это самый важный факт на этой странице, и раздел о private его объясняет.

Что разрешает каждый модификатор

МодификаторВнутри классаВ подклассеСнаружиСоблюдается во время выполнения
public (по умолчанию)дададасоблюдать нечего
protectedдаданетнет
privateданетнетнет
#name (JavaScript)данетнетда
readonlyчтение, а запись в конструкторечтениечтениенет

Модификаторы работают для полей, методов, геттеров и сеттеров, конструкторов и свойств-параметров (constructor(private id: string)). Писать public необязательно, и многие кодовые базы его опускают.

private это проверка времени компиляции

TypeScript стирает private вместе с остальными типами. В скомпилированном классе это обычное свойство, поэтому его видит всё, что не проходит через проверку типов: вызывающий код на обычном JavaScript, JSON.stringify, Object.keys и даже собственная запись TypeScript через квадратные скобки, которая разрешена как намеренная лазейка.

Для задачи, ради которой существует private, этого достаточно: сообщить другим разработчикам (и вашему редактору), что член это деталь реализации. Это не граница безопасности, и поле private окажется в логах и ответах JSON, если вы сами его не уберёте.

Поля #private: соблюдаются во время выполнения

В JavaScript есть собственные приватные поля, которые пишутся через #. Их соблюдает движок: вне класса obj.#field даже не является корректным синтаксисом, а поле не показывается в Object.keys, JSON.stringify или console.log.

Запись s.#token вне класса это ошибка TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier), и в отличие от случая с private обойти её нельзя и во время выполнения. Правила времени выполнения описаны в разделе приватные поля в JavaScript.

private или #private: что использовать

private x#x
Кто проверяеткомпилятордвижок JavaScript
Видно в Object.keys / JSON.stringifyданет
Доступ через скобки obj["x"]разрешённевозможен
Подкласс может объявить свой xнет, будет конфликтда, у каждого класса свой #x
Копируется при развёртывании { ...obj }данет
Синтаксис для методовprivate helper()#helper()

Используйте #private, когда данные должны оставаться приватными во время выполнения (токены, внутреннее состояние, которое пользователи библиотеки не должны трогать) или когда нужно, чтобы JSON.stringify их пропускал. Используйте private, когда цель только в чистом публичном API, когда фреймворку нужно читать поле через рефлексию или чтобы соответствовать существующему стилю кодовой базы. Не сочетайте их: private #x это ошибка TS18010 (An accessibility modifier cannot be used with a private identifier).

protected и подклассы

Член protected доступен внутри класса и в любом классе, который его расширяет, но не через экземпляры снаружи.

Одно правило удивляет. Внутри Polygon можно читать sides у this или у другого Polygon, но не у обычного Shape, переданного параметром: other.sides при other: Shape это ошибка TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). Подкласс может обращаться к защищённым членам только тех объектов, которые принадлежат его собственной ветви иерархии.

Подкласс может сделать защищённый член публичным, переобъявив его, но не может сделать публичный член защищённым или приватным. Переобъявите его с инициализатором (public override sides = 6) или только как тип (declare public sides: number). Простое public sides: number; отвергается с TS2564 и TS2612, потому что при современных полях классов оно сбросило бы унаследованное значение в undefined после возврата из super().

readonly

readonly запрещает переприсваивание после создания объекта. Поле можно задать в объявлении или в конструкторе; любое более позднее присваивание это ошибка TS2540.

Здесь видны два ограничения. readonly поверхностный: массив нельзя заменить, но его содержимое может меняться (чтобы запретить push, типизируйте его как readonly string[]). И как private, он исчезает во время выполнения, поэтому присваивание, которое отверг компилятор, всё равно выполнилось. Для значения, которое не должно меняться во время выполнения, используйте Object.freeze или геттер без сеттера. Readonly<T> и readonly-массивы описаны на странице о readonly.

readonly сочетается с модификаторами доступа: private readonly cache = new Map<string, number>() это распространённый приём для поля, которое внутреннее и никогда не переприсваивается.

Частые ошибки

  • Считать private защитой. Во время выполнения его нет. Используйте #field для данных, которые должны оставаться скрытыми, и никогда не отправляйте объект с секретами прямо в JSON.stringify.
  • Писать public везде. Это значение по умолчанию; его добавление ничего не меняет.
  • Использовать protected для всего «внутреннего». Если ни одному подклассу это не нужно, private оставляет поверхность меньше.
  • Ожидать, что readonly заморозит вложенные данные. Он только запрещает переприсваивать само свойство.

Часто задаваемые вопросы

Какие модификаторы доступа есть в TypeScript?

public (по умолчанию: доступен везде), protected (внутри класса и его подклассов) и private (только внутри класса). readonly это отдельный модификатор, который запрещает переприсваивание после конструктора и сочетается с любым из трёх.

Чем private отличается от #private в TypeScript?

private проверяет только компилятор; в сгенерированном JavaScript это обычное свойство, поэтому obj["secret"], JSON.stringify и Object.keys его видят. #secret это приватное поле JavaScript: его соблюдает среда выполнения, и код вне класса вообще не может его прочитать.

private в TypeScript действительно приватный?

Только при компиляции. После компиляции поле становится обычным свойством, которое может прочитать любой код на JavaScript. TypeScript даже разрешает доступ к приватным членам через квадратные скобки (obj["field"]) как лазейку. Используйте #field, когда приватность должна сохраняться во время выполнения.

Чем protected отличается от private в TypeScript?

Член private виден только внутри класса, который его объявляет. Член protected виден ещё и внутри подклассов. Ни к одному из них нельзя обратиться через экземпляр снаружи иерархии классов.

Можно ли изменить readonly свойства в TypeScript?

Свойство readonly можно присвоить в его объявлении или в конструкторе и больше нигде (иначе TS2540). Оно поверхностное: readonly tags: string[] нельзя заменить, но tags.push() по-прежнему работает. Чтобы запретить и это, используйте readonly string[].

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ