Symbol и приватность

Symbol-ключи не перечисляются стандартными методами (for...in, Object.keys(), JSON.stringify), что позволяет использовать их как псевдо-приватные свойства объектов до появления нативных приватных полей класса (#field).

Зачем нужно

До ES2022 с приватными полями класса Symbol был основным способом скрыть «внутренние» свойства объекта от случайного перечисления и конфликтов имён. Это полезно в библиотеках: metadata, internal state, уникальные маркеры не загрязняют публичный API объекта.

Где используется

  • Хранение внутреннего состояния в объектах без загрязнения namespace
  • Уникальные маркеры типов (type tags) в библиотеках
  • Метаданные объектов, не видимые пользователю
  • Расширение сторонних объектов без риска конфликтов

Основной контент

Symbol как скрытые ключи

// Symbol-ключи невидимы для стандартных перечислений
const _private = Symbol('_private');
const _version = Symbol('_version');

const obj = {
  name: 'PublicAPI',
  [_private]: 'secret data',
  [_version]: '2.1.0',
  getValue { return this[_private]; }
};

// Обычное перечисление не видит Symbol
console.log(Object.keys(obj));    // ['name', 'getValue'] — без Symbol!
console.log(JSON.stringify(obj));  // '{"name":"PublicAPI"}' — без Symbol!

for (const key in obj) {
  console.log(key); // только 'name', 'getValue'
}

// Но доступны напрямую если есть ссылка на Symbol
console.log(obj[_private]); // 'secret data'

// И через специальный метод
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(_private), Symbol(_version)]

Сравнение с WeakMap для приватности

// Вариант 1: Symbol (псевдо-приватный)
const _count = Symbol('count');
class Counter {
  constructor { this[_count] = 0; }
  increment { this[_count]++; }
  get { return this[_count]; }
}
// _count виден через Object.getOwnPropertySymbols

// Вариант 2: WeakMap (настоящая приватность)
const _state = new WeakMap();
class SecureCounter {
  constructor { _state.set(this, { count: 0 }); }
  increment { _state.get(this).count++; }
  get { return _state.get(this).count; }
}
// Состояние вообще недоступно извне

// Вариант 3: Private fields ES2022 (самый современный)
class ModernCounter {
  #count = 0;
  increment { this.#count++; }
  get { return this.#count; }
}

Symbol как уникальные type tags

// Уникальный маркер типа — не конфликтует с другими библиотеками
const OBSERVABLE = Symbol('Observable');
const IMMUTABLE = Symbol('Immutable');

function makeObservable(obj) {
  return { ...obj, [OBSERVABLE]: true };
}

function isObservable(obj) {
  return obj[OBSERVABLE] === true;
}

const state = makeObservable({ count: 0 });
console.log(isObservable(state));  // true
console.log(isObservable({ count: 0 })); // false

// Не конфликтует: другая библиотека создаст свой Symbol('Observable')
const LIB_OBSERVABLE = Symbol('Observable'); // другой Symbol!
console.log(OBSERVABLE === LIB_OBSERVABLE); // false

Расширение сторонних объектов

// Безопасное добавление свойств к объектам сторонних API
const METADATA = Symbol('metadata');

function attachMetadata(apiResponse, meta) {
  apiResponse[METADATA] = meta;
  return apiResponse;
}

const user = attachMetadata(
  { id: 1, name: 'Иван' }, // сторонний объект
  { fetchedAt: Date.now(), source: 'api' }
);

// Публичный API объекта не изменён
console.log(Object.keys(user));  // ['id', 'name']
console.log(user[METADATA]);     // { fetchedAt: ..., source: 'api' }

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

  • Symbol — не настоящая приватностьObject.getOwnPropertySymbols раскрывает все Symbol-ключи. Для истинной приватности используйте приватные поля (#field) или WeakMap.
  • Потеря Symbol при копированииObject.assign({}, obj) и {...obj} НЕ копируют Symbol-ключи. Используйте Object.assign с явным указанием или Object.getOwnPropertySymbols для ручного копирования.
  • Сериализация — Symbol-свойства невидимы для JSON.stringify. Если нужна сериализация этих данных, Symbol не подходит.

Связанные темы

Ресурсы


🎓 Источник: Архив 2018 - Часть 5 EventEmitter, Symbol, Proxy

  • 📅 2020-01-05 · YouTube
  • Тезисы: Symbol даёт частичную приватность — не виден в for...in, Object.keys(), JSON.stringify. Но доступен через Object.getOwnPropertySymbols или Reflect.ownKeys. Для полной приватности — WeakMap или #field

🎓 Источник: Explicit resource management with Using и Symbol.dispose

  • 📅 2025-05-22 · YouTube
  • Тезисы: Well-known Symbols используются для языковых протоколов. Symbol.dispose — proposal для автоматической очистки ресурсов (аналог IDisposable)