static и private #
Статические члены принадлежат самому классу, а не экземпляру. Приватные поля и методы с префиксом # доступны только внутри тела класса — движок запрещает обращение снаружи.
static методы #
Вызываются на классе, не на экземпляре:
class Lesson {
constructor(title, slug) {
this.title = title
this.slug = slug
}
static fromSlug(slug) {
const title = slug.replace(/-/g, ' ')
return new Lesson(title, slug)
}
static isValidSlug(slug) {
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(slug)
}
}
Lesson.isValidSlug('event-loop') // true
const lesson = Lesson.fromSlug('event-loop')
lesson.title // "event loop"
Фабрики, валидация и утилиты без состояния экземпляра — типичные кейсы для static.
static поля #
Поля на уровне класса (ES2022):
class CourseCatalog {
static maxFreeLessons = 3
static siteName = 'JSFlow'
constructor(tier) {
this.tier = tier
}
canOpenLesson(index) {
if (this.tier === 'pro') return true
return index < CourseCatalog.maxFreeLessons
}
}
new CourseCatalog('free').canOpenLesson(2) // true
new CourseCatalog('free').canOpenLesson(5) // false
Статические поля можно инициализировать выражением; они общие для всех экземпляров.
Приватные поля #
Имя начинается с # и объявляется в классе:
class Enrollment {
#token
#attempts = 0
constructor(token) {
this.#token = token
}
verify(input) {
this.#attempts += 1
return input === this.#token
}
getAttempts() {
return this.#attempts
}
}
const e = new Enrollment('secret-42')
e.verify('wrong') // false
e.getAttempts() // 1
// e.#token — SyntaxError снаружи класса
Приватность на уровне языка: не переименование вручную, а жёсткая проверка при компиляции/парсинге.
Приватные методы #
class PriceCalculator {
#vatRate
constructor(vatRate) {
this.#vatRate = vatRate
}
#withVat(net) {
return net * (1 + this.#vatRate)
}
quote(net) {
const gross = this.#withVat(net)
return `${gross.toFixed(2)} ₽`
}
}
new PriceCalculator(0.2).quote(1000) // "1200.00 ₽"
Вспомогательная логика, которую не должны вызывать потребители API, прячется за #.
static private #
Статические приватные члены тоже поддерживаются:
class IdGenerator {
static #seq = 0
static next() {
IdGenerator.#seq += 1
return IdGenerator.#seq
}
}
IdGenerator.next() // 1
IdGenerator.next() // 2
// IdGenerator.#seq — ошибка
Удобно для счётчиков и кэшей на уровне класса без глобальных переменных.
private vs замыкание vs _ #
| Подход | Доступ снаружи | Проверка |
|---|---|---|
#field | запрещён | язык |
| замыкание в модуле | нет прямого | соглашение |
_field (подчёркивание) | возможен | только договорённость |
// _ не делает поле приватным
class Legacy {
constructor() {
this._internal = 1
}
}
const l = new Legacy()
l._internal = 99 // ничто не мешает
Для публичных библиотек и командного кода # даёт явную границу.
Наследование и private #
Приватные члены не наследуются в смысле доступа: подкласс не может обратиться к #field родителя:
class Base {
#secret = 10
getSecret() {
return this.#secret
}
}
class Derived extends Base {
double() {
// return this.#secret * 2 — SyntaxError
return this.getSecret() * 2
}
}
Защищённый доступ между уровнями иерархии — через публичные или protected-подобные методы без #, либо через композицию.
Частые ошибки #
- Вызов static метода на экземпляре — часто работает через прототип класса, но путает читателей; пишите
Lesson.fromSlug(), неlesson.constructor.fromSlug()без нужды. - # в объекте-литерале — приватные поля только внутри
class. - Сериализация —
JSON.stringifyне видит#поля; для экспорта нужны явные методы. - Ожидание private у старых сред — нужна поддержка ES2022; проверяйте целевые браузеры или транспиляцию.
Итог #
static— методы и поля на классе: фабрики, константы, утилиты.#— настоящая приватность на экземпляре и на классе для static private.- Подклассы не лезут в
#родителя; используйте публичный API. - Подчёркивание в имени — соглашение, не защита;
#— проверка движка.