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()безnew—new 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».