Menu
Русский

Приватные поля в JavaScript: синтаксис #

Разбираем, как префикс # делает поля и методы класса по-настоящему приватными в JavaScript — синтаксис, правила и почему подчёркивания уже недостаточно.

Проблема с подчёркиваниями

Долгое время в JavaScript просто не было приватных полей. Вместо них использовали договорённость: к имени свойства добавляли подчёркивание и надеялись, что остальные разработчики не полезут туда, куда не надо:

index.js
Output
Click Run to see the output here.

_count выглядит приватным, но на самом деле это не так. Любой вызывающий код может его прочитать, перезаписать или удалить. Подчёркивание — это всего лишь вежливая табличка на двери; сама дверь нараспашку.

Современный JavaScript решил эту проблему — появились настоящие приватные поля, которые помечаются символом #.

Символ # делает поле приватным

Добавьте префикс # к имени поля в объявлении и в каждом месте, где вы к нему обращаетесь:

index.js
Output
Click Run to see the output here.

Внутри класса this.#count работает как обычное поле. А вот снаружи его просто не существует:

index.js
Output
Click Run to see the output here.

# — это буквально часть имени поля. Это не модификатор вроде ключевого слова private из других языков, а специальный символ, по которому парсер ищет отдельный защищённый слот у объекта. Именно поэтому ошибка вылезает ещё на этапе парсинга — до того, как код вообще запустится.

Приватные методы и геттеры

Приватными можно делать не только поля. Методы, геттеры и сеттеры тоже прекрасно работают с префиксом #:

index.js
Output
Click Run to see the output here.

#assertPositive — это внутренний помощник. В публичное API он не входит, и если сделать его по-настоящему приватным, никто не вызовет его извне по ошибке. А раз от него никто не зависит, вы спокойно сможете переименовать или удалить его позже.

Приватные статические поля и методы

Статические члены тоже можно делать приватными. Префикс # работает точно так же:

index.js
Output
Click Run to see the output here.

Приватные статические поля живут на самом классе, а не на экземплярах. Удобно для счётчиков, кэшей или конфигурации, которая не должна утекать наружу.

Наследники их не видят

Здесь часто спотыкаются те, кто пришёл из Java или C#. В JavaScript приватные поля принадлежат классу, а не экземпляру. Подкласс не может залезть в приватные поля родителя:

index.js
Output
Click Run to see the output here.

В JavaScript нет модификатора protected. Если подклассу нужен доступ к данным, родитель должен предоставить метод, геттер или (что встречается реже) обычное, непривaтное поле. Это сделано намеренно: приватное — значит действительно приватное, и наследование не проделывает в нём дырок.

Проверка приватного поля через in

Иногда нужно убедиться, что объект действительно принадлежит вашему классу — это так называемый brand check. Внутри класса оператор in умеет работать с именами приватных полей:

index.js
Output
Click Run to see the output here.

Поскольку создавать объекты с #balance умеет только Wallet, проверка #balance in obj — надёжный способ убедиться, что obj действительно экземпляр Wallet. В некоторых пограничных случаях это быстрее и безопаснее, чем instanceof: приватные поля невозможно подделать снаружи.

Типичная ловушка: у обычных объектов приватных полей нет

Приватные поля существуют только у экземпляров, созданных конструктором класса. Если попытаться обратиться к такому полю у объекта, созданного без new, получим ошибку:

index.js
Output
Click Run to see the output here.

Вызов метода с this, который не является экземпляром Point, выбросит ошибку прямо в рантайме. Именно так работает brand check, о котором шла речь выше: приватные поля привязаны к конкретному классу, в котором они объявлены, а не к произвольному объекту с подходящей формой.

Когда использовать #

По умолчанию делайте поля приватными всегда, когда состояние или вспомогательный метод не входит в публичный API класса. Почему это удобно:

  • Свобода рефакторинга. Внешний код просто не может зацепиться за то, что ему не видно.
  • Настоящая инкапсуляция в JavaScript. Никаких случайных чтений, записей или удалений снаружи.
  • Чистый автокомплит. Редактор не подсовывает приватные члены тем, кто пользуется классом извне.

Публичные свойства оставляйте для того, что действительно является частью интерфейса. Если нужен доступ к приватному полю только на чтение — заведите геттер (get name()). А от соглашения с подчёркиванием (_field) пора отказаться: это был костыль на время, пока в языке не было настоящей приватности. Теперь она есть.

index.js
Output
Click Run to see the output here.

#celsius — это скрытое хранилище, а celsius и fahrenheit — представления только для чтения. Снаружи внутреннее состояние не испортить, а сам класс в любой момент может поменять способ хранения значения.

Дальше: прототипы

Классы в JavaScript — это по большей части синтаксический сахар над системой прототипов, более старой и фундаментальной моделью, на которой язык, собственно, и построен. Разобравшись с прототипами, вы поймёте, почему this ведёт себя именно так, как на самом деле работает наследование и что скрывается за extends у класса. Об этом — на следующей странице.

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

Как объявить приватное поле в JavaScript?

Нужно поставить # перед именем поля — причём и при объявлении, и при каждом обращении. Например: class Counter { #count = 0; increment() { this.#count++; } } — это настоящее приватное поле. Важный момент: # — это часть имени, а не отдельный оператор.

В чём разница между #field и _field?

_field — это просто договорённость между разработчиками: свойство остаётся публичным, и любой код снаружи спокойно его читает и меняет. А #field контролирует сам язык: снаружи класса к полю не подобраться никак, попытка обратиться вызовет SyntaxError ещё на этапе парсинга. Если нужна реальная инкапсуляция — только #.

Могут ли подклассы обращаться к приватным полям родителя?

Нет. Приватные поля видны только внутри того класса, где они объявлены — даже наследники до них не дотянутся. Если подклассу нужен доступ, родительский класс должен сам предоставить метод или геттер. Это строже, чем protected в других языках, и сделано так сознательно.

Можно ли проверить, есть ли у объекта приватное поле?

Да, через оператор in внутри класса: выражение #field in obj вернёт true, если у obj есть это приватное поле. Удобно для brand-проверок — чтобы убедиться, что объект действительно экземпляр вашего класса, прежде чем дёргать его методы.

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

НАЧАТЬ