JavaScriptMiddleTechnical

Объясните Symbol в JavaScript и в каких случаях его применять.

Symbol — уникальный примитивный тип, гарантирующий отсутствие коллизий ключей. Используется для приватных/метаданных свойств, well-known symbols (@@iterator, @@toPrimitive) и констант перечислений.

Что такое Symbol

Symbol — примитивный тип, введённый в ES2015. Каждый вызов Symbol() создаёт значение, гарантированно уникальное во всей программе. Символы не сериализуются в JSON и не перечисляются обычными методами объекта.

const s1 = Symbol("id");
const s2 = Symbol("id");
console.log(s1 === s2); // false — всегда уникальны
console.log(typeof s1); // "symbol"

Основные применения

1. Уникальные ключи свойств без коллизий

Библиотека может добавить приватные метаданные в чужой объект, не рискуя затереть поля пользователя:

const _cache = Symbol("cache");

function withCache(obj) {
  obj[_cache] = {};
  return obj;
}

const user = { name: "Alice" };
withCache(user);

console.log(Object.keys(user));          // ["name"] — _cache не виден
console.log(JSON.stringify(user));       // {"name":"Alice"} — не сериализуется
console.log(user[_cache]);               // {} — доступно по ссылке на символ

2. Well-known symbols — встроенные протоколы

JavaScript использует предопределённые символы для кастомизации поведения объектов:

// Symbol.iterator — сделать объект итерируемым
class Range {
  constructor(start, end) {
    this.start = start;
    this.end = end;
  }
  [Symbol.iterator]() {
    let current = this.start;
    const end = this.end;
    return {
      next() {
        return current <= end
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
}

const range = new Range(1, 5);
console.log([...range]); // [1, 2, 3, 4, 5]

// Symbol.toPrimitive — управление приведением типов
const money = {
  amount: 100,
  currency: "USD",
  [Symbol.toPrimitive](hint) {
    if (hint === "number") return this.amount;
    if (hint === "string") return `${this.amount} ${this.currency}`;
    return this.amount; // default
  }
};
console.log(+money);     // 100
console.log(`${money}`); // "100 USD"

// Symbol.hasInstance — кастомный instanceof
class EvenNumber {
  static [Symbol.hasInstance](n) {
    return typeof n === "number" && n % 2 === 0;
  }
}
console.log(4 instanceof EvenNumber); // true
console.log(3 instanceof EvenNumber); // false

3. Константы-перечисления без риска совпадений

const Direction = {
  UP:    Symbol("UP"),
  DOWN:  Symbol("DOWN"),
  LEFT:  Symbol("LEFT"),
  RIGHT: Symbol("RIGHT")
};

// Нельзя случайно сравнить с произвольной строкой
function move(dir) {
  if (dir === Direction.UP) console.log("moving up");
}
move(Direction.UP); // "moving up"
move("UP");         // ничего — строка !== Symbol

4. Symbol.for — глобальный реестр

Для символов, которые должны быть одинаковы в разных модулях или iframe:

const s1 = Symbol.for("app.userId");
const s2 = Symbol.for("app.userId");
console.log(s1 === s2); // true — один и тот же символ из реестра

console.log(Symbol.keyFor(s1)); // "app.userId"

Подводные камни

  • Символы не попадают в JSON.stringify, Object.keys, for...in — это удобно для приватных полей, но неожиданно, если нужна сериализация.
  • Object.getOwnPropertySymbols(obj) всё равно возвращает все символьные ключи — настоящей приватности нет; для этого используйте #privateField (class fields).
  • Symbol() без newnew Symbol() бросает TypeError; это примитив, не объект.
  • Symbol.for разделяет реестр между realms в одном процессе, но не между iframe с разным origin.
  • Описание символа (Symbol("desc")) — только для отладки; два символа с одинаковым описанием не равны.
  • Spread { ...obj } и Object.assign копируют символьные свойства — они не полностью «невидимы».
  • TypeScript требует явного объявления индекса через [key: symbol]: unknown в интерфейсах, иначе symbol-ключи не типизируются.

Common mistakes

  • Смешивать «Symbol» с похожим механизмом без критерия выбора.
  • Игнорировать риск: неверно оценить границы применения темы «Symbol» и получить хрупкое решение.
  • Показывать только синтаксис и не объяснять поведение в runtime или сборке.

What the interviewer is testing

  • Объясняет уникальные property keys и встроенные well-known символы.
  • Показывает на примере, как работает: каждый Symbol() уникален, даже с одинаковым description; символы полезны для избежания коллизий ключей и настройки протоколов вроде iteration через Symbol.iterator.
  • Называет production-нюанс и граничный случай для темы «Symbol».

Sources

Related topics