static и private #

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-подобные методы без #, либо через композицию.


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

  1. Вызов static метода на экземпляре — часто работает через прототип класса, но путает читателей; пишите Lesson.fromSlug(), не lesson.constructor.fromSlug() без нужды.
  2. # в объекте-литерале — приватные поля только внутри class.
  3. Сериализация — JSON.stringify не видит # поля; для экспорта нужны явные методы.
  4. Ожидание private у старых сред — нужна поддержка ES2022; проверяйте целевые браузеры или транспиляцию.

Итог #

  • static — методы и поля на классе: фабрики, константы, утилиты.
  • # — настоящая приватность на экземпляре и на классе для static private.
  • Подклассы не лезут в # родителя; используйте публичный API.
  • Подчёркивание в имени — соглашение, не защита; # — проверка движка.

См. также #