Как сделать приватный метод в JavaScript
Что проверяют
Знаете ли вы современный синтаксис классов и понимаете ли, что приватность в JavaScript бывает настоящей и договорной. Следом часто спрашивают, чем # отличается от private в TypeScript и как такой метод тестировать.
Как отвечать
Основной ответ: приватные методы через #, стандарт ES2022, поддерживаются всеми актуальными браузерами и Node.js.
class Counter {
#count = 0;
#log() { console.log(this.#count); }
inc() { this.#count++; this.#log(); }
}Что важно сказать про них:
- жёсткая приватность: обращение
obj.#logвне тела класса это синтаксическая ошибка, из обычного кода метод не достать, его не покажут ниObject.keys, ниReflect.ownKeys(в DevTools он при этом виден); - нет доступа у наследников: подкласс не видит
#-члены родителя, аналогаprotectedв языке нет; - статические варианты:
static #helper()для приватной служебной функции класса; - проверка бренда:
#count in objпоказывает, создан ли объект этим классом.
Альтернативы, которые стоит знать:
- замыкание: функция внутри конструктора или модуля. Работает везде, но при создании в конструкторе у каждого экземпляра своя копия функции;
WeakMap: приватные данные по ключу-экземпляру, так делали до ES2022 и так делают транспиляторы;Symbolи префикс_: только договорённость, символ достаётся черезObject.getOwnPropertySymbols;privateв TypeScript: проверка только на этапе компиляции, в собранном JS это обычное свойство.
Где спотыкаются
Называют private из TypeScript настоящей приватностью. Если нужна защита в рантайме, в TS тоже пишут #.
Не знают про ловушку с Proxy: вызов #-метода через прокси падает с TypeError, потому что у прокси нет приватного слота. На этом ломаются реактивные обёртки на прокси, например reactive() во Vue, когда в них попадает экземпляр класса с #.
Хотят протестировать приватный метод напрямую. Правильный ответ: он проверяется через публичное поведение, а если его очень хочется проверить отдельно, это повод вынести логику в самостоятельную функцию или модуль.