Symbol
Symbol — примитивный тип данных, значения которого гарантированно уникальны. Используется как идентификатор свойств объекта.
Зачем нужно
Symbol решает проблему коллизии имён свойств. Когда несколько библиотек добавляют свойства к одному объекту, символы гарантируют, что они не перезапишут друг друга.
Где используется
- Уникальные ключи свойств объектов
- Метапрограммирование (well-known symbols)
- Скрытые свойства (не видны в
for...in) - Реализация итераторов (
Symbol.iterator)
Предпосылки
Создание Symbol
// Каждый Symbol уникален
const sym1 = Symbol;
const sym2 = Symbol;
console.log(sym1 === sym2); // false
// Описание (для отладки)
const id = Symbol('id');
const name = Symbol('id'); // Описание одинаковое...
console.log(id === name); // false — но символы разные!
console.log(id.toString()); // "Symbol(id)"
console.log(id.description); // "id"
// Symbol — НЕ конструктор
// const sym = new Symbol(); // TypeError!
Symbol как ключ объекта
const id = Symbol('id');
const user = {
name: 'Алиса',
[id]: 42 // символьное свойство
};
console.log(user[id]); // 42
console.log(user.id); // undefined — это другое свойство!
// Символьные свойства скрыты от обычного перебора
console.log(Object.keys(user)); // ['name']
console.log(JSON.stringify(user)); // '{"name":"Алиса"}'
for (const key in user) {
console.log(key); // только 'name'
}
// Но их можно получить явно
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]
console.log(Reflect.ownKeys(user)); // ['name', Symbol(id)]
Symbol.for — глобальный реестр
// Symbol.for ищет или создаёт символ в глобальном реестре
const s1 = Symbol.for('app.id');
const s2 = Symbol.for('app.id');
console.log(s1 === s2); // true — один и тот же символ!
// Symbol.keyFor — получить ключ из реестра
console.log(Symbol.keyFor(s1)); // "app.id"
// Обычные символы не в реестре
const local = Symbol('local');
console.log(Symbol.keyFor(local)); // undefined
Well-known Symbols (Встроенные символы)
// Symbol.iterator — определяет итерацию
const range = {
from: 1,
to: 5,
[Symbol.iterator] {
let current = this.from;
const last = this.to;
return {
next {
return current <= last
? { value: current++, done: false }
: { done: true };
}
};
}
};
for (const num of range) {
console.log(num); // 1, 2, 3, 4, 5
}
// Symbol.toPrimitive — кастомное преобразование типов
const money = {
amount: 100,
currency: 'RUB',
[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 RUB"
console.log(money + 50); // 150
// Symbol.hasInstance — кастомный instanceof
class Even {
static [Symbol.hasInstance](num) {
return typeof num === 'number' && num % 2 === 0;
}
}
console.log(4 instanceof Even); // true
console.log(3 instanceof Even); // false
// Symbol.toStringTag — кастомный Object.prototype.toString()
class MyClass {
get [Symbol.toStringTag] {
return 'MyClass';
}
}
console.log(Object.prototype.toString().call(new MyClass));
// "[object MyClass]"
Практическое применение
Приватные свойства (до появления #private)
const _balance = Symbol('balance');
class BankAccount {
constructor(initial) {
this[_balance] = initial;
}
deposit(amount) {
this[_balance] += amount;
}
get balance {
return this[_balance];
}
}
const account = new BankAccount(1000);
account.deposit(500);
console.log(account.balance); // 1500
console.log(account._balance); // undefined — не доступно по строке
Избежание коллизий
// Библиотека A добавляет свойство
const libA_id = Symbol('id');
function enrichByLibA(obj) {
obj[libA_id] = 'data from A';
}
// Библиотека B тоже добавляет 'id'
const libB_id = Symbol('id');
function enrichByLibB(obj) {
obj[libB_id] = 'data from B';
}
const obj = {};
enrichByLibA(obj);
enrichByLibB(obj);
// Обе библиотеки работают без конфликтов
console.log(obj[libA_id]); // 'data from A'
console.log(obj[libB_id]); // 'data from B'
Частые ошибки
1. Symbol не конвертируется в строку неявно
const sym = Symbol('test');
// console.log('Symbol: ' + sym); // TypeError!
console.log('Symbol: ' + sym.toString()); // OK
console.log(`Symbol: ${sym.description}`); // OK
2. Путаница Symbol и Symbol.for
Symbol('id') === Symbol('id'); // false — разные
Symbol.for('id') === Symbol.for('id'); // true — из реестра
Практика
- Создай объект с символьным свойством и покажи, что оно скрыто от
Object.keys() - Реализуй
Symbol.iteratorдля объекта, который перебирает чётные числа от 0 до N - Используй
Symbol.toPrimitiveдля объектаTemperatureс автоматическим преобразованием
Связанные темы
Ресурсы
🎓 Источник: Proxy и Symbol в JavaScript
- 📅 2018-11-26 · YouTube
- Тезисы: Symbol — уникальный идентификатор. Well-known symbols (
Symbol.iterator,Symbol.toPrimitive) — это протоколы языка для меточек интеграции с runtime. Symbol.for — глобальный shared registry
🎓 Источник: Архив 2018 - Часть 5 EventEmitter, Symbol, Proxy
- 📅 2020-01-05 · YouTube
- Тезисы: Symbol — это "имя без коллизий". В EventEmitter можно использовать Symbol как тип события — гарантия отсутствия конфликта с другими подписчиками
🎓 Источник: Explicit resource management with Using and Symbol.dispose
- 📅 2025-05-22 · YouTube
- Тезисы: новый proposal —
usingstatement +Symbol.dispose/Symbol.asyncDispose. АналогIDisposableиз C#, RAII из C++. Автоматически вызываетdisposeпри выходе из блока