Static & Private #fields
В одной фразе: static поля/методы принадлежат классу, не экземплярам; # private поля — жёсткая инкапсуляция на уровне спецификации (не через closure или Symbol, а через [[PrivateFieldValues]]). На собесе часто спрашивают как наследуются static и как сделать brand-check для private.
// Singleton через static privateclass 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)); // truePoint.isPoint({}); // falseИтог: Private # поля — реальная инкапсуляция: не обойти через Proxy, Reflect или prototype tricks. Static поля наследуются через [[Prototype]] цепочку самих классов.