ДокументацияJavaScriptSymbol в JavaScript — для чего нужны и как использовать
Средний 7 мин чтения

Symbol в JavaScript — для чего нужны и как использовать

Symbol в JavaScript — уникальные идентификаторы, Symbol.iterator, Symbol.toPrimitive, глобальный реестр Symbol.for и практические примеры.

SymbolSymbol.iteratorSymbol.forуникальный идентификаторJavaScript

Что такое Symbol

Symbol — это уникальный примитив. Каждый вызов Symbol() создаёт новое значение, которое гарантированно не совпадёт ни с каким другим:

const a = Symbol('id')
const b = Symbol('id')

a === b        // false — всегда разные
typeof a       // 'symbol'
a.description  // 'id' — описание для отладки

Строка в скобках — описание, оно ни на что не влияет. Только для удобства разработчика.

Зачем нужен Symbol

Уникальные ключи объекта

Обычные строковые ключи могут столкнуться:

const library = { name: 'Моя библиотека' }

// Другой код может перезаписать 'name'
library.name = 'Чужая библиотека'

Symbol-ключи никогда не столкнутся:

const library = { name: 'Моя библиотека' }

const myId = Symbol('id')
library[myId] = 123

const otherId = Symbol('id')
library[otherId] = 456

console.log(library[myId])   // 123 — не перезаписан
console.log(library[otherId]) // 456

Symbol-ключи скрыты от обычного перебора

const secretKey = Symbol('secret')

const user = {
  name: 'Анна',
  age: 25,
  [secretKey]: 'пароль123',
}

Object.keys(user)          // ['name', 'age'] — Symbol не виден
Object.values(user)        // ['Анна', 25]
JSON.stringify(user)       // '{"name":"Анна","age":25}' — Symbol не попал в JSON

for (const key in user) {
  console.log(key)         // 'name', 'age'
}

Чтобы получить Symbol-ключи — Object.getOwnPropertySymbols():

Object.getOwnPropertySymbols(user) // [Symbol(secret)]

Все ключи включая Symbol — Reflect.ownKeys():

Reflect.ownKeys(user) // ['name', 'age', Symbol(secret)]

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

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

const a = Symbol.for('app.id')
const b = Symbol.for('app.id')

a === b // true — один и тот же Symbol

Symbol.keyFor(a) // 'app.id' — получить ключ из реестра

Обычный Symbol('app.id') каждый раз создаёт новый. Symbol.for — находит существующий.

Встроенные Symbol (Well-known Symbols)

JavaScript определяет набор Symbol-значений, которые управляют поведением объектов.

Symbol.iterator

Делает объект перебираемым через for...of:

const range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    let current = this.from
    const last = this.to

    return {
      next: () => {
        if (current <= last) return { value: current++, done: false }
        return { done: true }
      },
    }
  },
}

for (const num of range) {
  console.log(num) // 1, 2, 3, 4, 5
}

[...range] // [1, 2, 3, 4, 5]

Symbol.toPrimitive

Определяет, как объект преобразуется в примитив (при +, ${}, сравнении):

const price = {
  amount: 1500,
  currency: 'RUB',

  [Symbol.toPrimitive](hint) {
    if (hint === 'string') return `${this.amount} ${this.currency}`
    if (hint === 'number') return this.amount
    return this.amount
  },
}

console.log(`Цена: ${price}`)   // 'Цена: 1500 RUB' — hint = 'string'
console.log(price + 500)        // 2000 — hint = 'default'
console.log(price * 2)          // 3000 — hint = 'number'

Symbol.toStringTag

Имя, которое возвращает Object.prototype.toString:

class ApiClient {
  [Symbol.toStringTag] = 'ApiClient'
}

const client = new ApiClient()
Object.prototype.toString.call(client) // '[object ApiClient]'

Symbol.hasInstance

Определяет поведение instanceof:

class EvenNumber {
  static [Symbol.hasInstance](num) {
    return typeof num === 'number' && num % 2 === 0
  }
}

42 instanceof EvenNumber // true
7 instanceof EvenNumber  // false

Symbol.species

Указывает, какой конструктор использовать для создания производных объектов:

class MyArray extends Array {
  static get [Symbol.species]() {
    return Array // map/filter вернут обычный Array, а не MyArray
  }
}

const arr = new MyArray(1, 2, 3)
const mapped = arr.map(x => x * 2)

arr instanceof MyArray    // true
mapped instanceof MyArray // false
mapped instanceof Array   // true

Практическое использование

«Скрытые» свойства для библиотек

const VISITED = Symbol('visited')

function markVisited(element) {
  element[VISITED] = true
}

function isVisited(element) {
  return !!element[VISITED]
}

// Не засоряет объект строковыми ключами, не конфликтует с другими библиотеками

Enum-подобные значения

const Status = Object.freeze({
  PENDING: Symbol('pending'),
  ACTIVE: Symbol('active'),
  DONE: Symbol('done'),
})

function handleStatus(status) {
  switch (status) {
    case Status.PENDING: return 'Ожидает'
    case Status.ACTIVE: return 'Активен'
    case Status.DONE: return 'Завершён'
    default: return 'Неизвестно'
  }
}

Symbol гарантирует, что значение нельзя подделать — строковые константы можно передать как строку, а Symbol — только из объекта Status.

Итог

  • Symbol() — уникальный идентификатор, Symbol.for() — глобальный реестр
  • Symbol-ключи скрыты от Object.keys, for...in, JSON.stringify
  • Встроенные Symbol (Symbol.iterator, Symbol.toPrimitive, Symbol.toStringTag) настраивают поведение объектов
  • Используйте для «скрытых» свойств, enum-значений и безопасного расширения объектов