Перейти к содержимому

Static & Private #fields

В одной фразе: static поля/методы принадлежат классу, не экземплярам; # private поля — жёсткая инкапсуляция на уровне спецификации (не через closure или Symbol, а через [[PrivateFieldValues]]). На собесе часто спрашивают как наследуются static и как сделать brand-check для private.

// Singleton через static private
class Config {
static #instance = null;
static getInstance() {
Config.#instance ??= new Config(); // создаём только при первом вызове
return Config.#instance;
}
#settings = new Map(); // private instance field
set(key, value) { this.#settings.set(key, value); return this; }
get(key) { return this.#settings.get(key); }
}
const cfg = Config.getInstance();
cfg.set('debug', true).set('theme', 'dark');
// cfg.#settings // SyntaxError: Private field '#settings' must be declared in an enclosing class
// static наследуется по цепочке классов
class Base {
static type = 'base';
// new this() — this здесь это сам класс (не экземпляр)!
static create(...args) { return new this(...args); }
}
class Derived extends Base {
static type = 'derived'; // перекрывает Base.type
}
Derived.type; // 'derived'
Base.type; // 'base'
Derived.create(); // экземпляр Derived (new this() = new Derived())
// Цепочка прототипов классов:
Object.getPrototypeOf(Derived) === Base; // true — static наследуются!
// Brand check: единственный способ проверить private field снаружи — в самом классе
class Point {
#x; #y;
constructor(x, y) { this.#x = x; this.#y = y; }
static isPoint(obj) {
return #x in obj; // true только для экземпляров Point
}
distanceTo(other) {
if (!Point.isPoint(other)) throw new TypeError('Expected a Point');
return Math.hypot(this.#x - other.#x, this.#y - other.#y);
}
}
Point.isPoint(new Point(0, 0)); // true
Point.isPoint({}); // false

Итог: Private # поля — реальная инкапсуляция: не обойти через Proxy, Reflect или prototype tricks. Static поля наследуются через [[Prototype]] цепочку самих классов.