Перейти к содержимому

83. Symbol

Symbol — уникальный примитивный тип данных в JavaScript. Каждый Symbol гарантированно уникален, даже если создан с одинаковым описанием. Используется для создания скрытых свойств объектов и реализации специального поведения через well-known symbols.

const sym1 = Symbol()
const sym2 = Symbol('описание')
const sym3 = Symbol('описание')
console.log(sym1 === sym2) // false — каждый Symbol уникален
console.log(sym2 === sym3) // false — даже с одинаковым описанием!
console.log(typeof sym1) // 'symbol'
console.log(sym2.toString()) // 'Symbol(описание)'
console.log(sym2.description) // 'описание'

Главное применение Symbol — создание «скрытых» свойств, которые не попадают в обычные итерации:

const id = Symbol('id')
const name = Symbol('name')
const user = {
[id]: 123,
[name]: 'Алексей',
login: 'alexei',
}
// Обычная итерация не показывает Symbol-свойства
console.log(Object.keys(user)) // ['login']
console.log(JSON.stringify(user)) // '{"login":"alexei"}'
// Получить Symbol-свойства можно явно
console.log(user[id]) // 123
console.log(Object.getOwnPropertySymbols(user)) // [Symbol(id), Symbol(name)]

Symbol.for(key) создаёт символ в глобальном реестре. Одинаковый ключ — один и тот же символ, даже из разных модулей:

const s1 = Symbol.for('shared')
const s2 = Symbol.for('shared')
console.log(s1 === s2) // true — один символ из реестра!
console.log(Symbol.keyFor(s1)) // 'shared'
// Обычный Symbol не попадает в реестр
const local = Symbol('local')
console.log(Symbol.keyFor(local)) // undefined

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

Позволяет сделать объект итерируемым:

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 }
: { value: undefined, done: true }
}
}
}
}
for (const num of range) {
console.log(num) // 1, 2, 3, 4, 5
}
console.log([...range]) // [1, 2, 3, 4, 5]

Управляет приведением объекта к примитиву:

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 (number hint)
console.log(`${money}`) // '100 USD' (string hint)
console.log(money + 50) // 150 (default hint)

Настраивает оператор instanceof:

class EvenNumber {
static [Symbol.hasInstance](num) {
return Number.isInteger(num) && num % 2 === 0
}
}
console.log(2 instanceof EvenNumber) // true
console.log(3 instanceof EvenNumber) // false
console.log(4 instanceof EvenNumber) // true
СимволНазначение
Symbol.iteratorИтерируемость (for...of, spread)
Symbol.asyncIteratorАсинхронная итерация
Symbol.toPrimitiveПриведение к примитиву
Symbol.toStringTagРезультат Object.prototype.toString
Symbol.hasInstanceПоведение instanceof
Symbol.speciesКонструктор для создания производных объектов

Практический пример: приватные поля через Symbol

Заголовок раздела «Практический пример: приватные поля через Symbol»
// Паттерн "скрытые свойства" через Symbol
const _balance = Symbol('balance')
const _owner = Symbol('owner')
class BankAccount {
constructor(owner, initialBalance) {
this[_owner] = owner
this[_balance] = initialBalance
}
deposit(amount) {
if (amount <= 0) throw new Error('Сумма должна быть положительной')
this[_balance] += amount
return this
}
withdraw(amount) {
if (amount > this[_balance]) throw new Error('Недостаточно средств')
this[_balance] -= amount
return this
}
toString() {
return `Счёт ${this[_owner]}: ${this[_balance]} руб.`
}
}
const account = new BankAccount('Алексей', 1000)
account.deposit(500).withdraw(200)
console.log(account.toString()) // 'Счёт Алексей: 1300 руб.'
// Свойство "скрыто" от обычного доступа
console.log(Object.keys(account)) // []
console.log(account.balance) // undefined
  1. Создайте Symbol SECRET_KEY и используйте его как ключ в объекте конфигурации.
  2. Реализуйте итерируемый класс Fibonacci, генерирующий числа Фибоначчи через Symbol.iterator.
  3. Добавьте Symbol.toPrimitive к классу Temperature для конвертации в Celsius/Fahrenheit.
  4. Используйте Symbol.for('validator') для создания общего символа валидации между модулями.