Symbol и перечисление ключей

Symbol и перечисление ключей #

Ключи объекта в JavaScript почти всегда строки; Symbol даёт гарантированно уникальный идентификатор. Методы Object.keys, values и entries перечисляют собственные перечисляемые свойства — это основа сериализации, валидации форм и обхода payload без захвата унаследованных полей из прототипа (подробнее — см. раздел ООП).


Object.keys, values, entries #

const cart = {
  id: 'cart-7a2',
  currency: 'RUB',
  itemCount: 2,
}

Object.keys(cart)
// ['id', 'currency', 'itemCount']

Object.values(cart)
// ['cart-7a2', 'RUB', 2]

Object.entries(cart)
// [['id', 'cart-7a2'], ['currency', 'RUB'], ['itemCount', 2]]

entries удобен для преобразования объекта в Map или для итерации пар ключ–значение:

for (const [key, value] of Object.entries(cart)) {
  console.log(`${key}: ${value}`)
}

const cartMap = new Map(Object.entries(cart))

Порядок ключей для строковых свойств, похожих на целые индексы, и остальных строк — по спецификации ES2015+ (сначала целочисленные ключи по возрастанию, затем остальные строки в порядке создания, затем Symbol).


Что не попадает в перечисление #

Object.keys не видит:

  • свойства из прототипа (унаследованные);
  • свойства с enumerable: false;
  • ключи типа Symbol (для них — Object.getOwnPropertySymbols).
const proto = { sharedMethod() {} }
const playlist = Object.create(proto)
playlist.title = 'Focus Tracks'
playlist.trackCount = 5

Object.keys(playlist) // ['title', 'trackCount'] — без sharedMethod

Проверка одного собственного ключа:

Object.hasOwn(playlist, 'title') // true
Object.hasOwn(playlist, 'sharedMethod') // false

Object.hasOwn предпочтительнее устаревшего obj.hasOwnProperty, если объект мог переопределить последний.


Symbol как ключ #

Symbol создаёт уникальное значение; два вызова Symbol('desc') не равны:

const internalId = Symbol('playlist.internalId')

const playlist = {
  title: 'Evening Mix',
  [internalId]: 'pl-9f2c',
}

playlist[internalId] // "pl-9f2c"
playlist.internalId // undefined — Symbol не строковый ключ с таким именем

Описание в Symbol('...') только для отладки; на уникальность не влияет.

Зачем Symbol в прикладном коде #

  1. Скрытые служебные поля — не конфликтуют со строковыми ключами пользователя и не попадают в Object.keys без явного обхода.
  2. Well-known symbolsSymbol.iterator, Symbol.toStringTag и др. настраивают поведение встроенных протоколов (итерация, строковое представление).
  3. Глобальный реестрSymbol.for('app.cart') возвращает один и тот же символ в рамках программы:
const CART_TAG = Symbol.for('app.cart')

const payload = { [CART_TAG]: { version: 1 } }
Symbol.keyFor(CART_TAG) // "app.cart"

Для публичных JSON API Symbol обычно не сериализуется — JSON.stringify его пропускает.


Обход всех собственных ключей #

Полный список собственных свойств (включая неперечисляемые) — Object.getOwnPropertyNames; символы — Object.getOwnPropertySymbols:

const profile = {
  userId: 'u-104',
  [Symbol('session')]: 'sess-9f2',
}

Object.getOwnPropertyNames(profile) // ['userId']
Object.getOwnPropertySymbols(profile) // [Symbol(session)]

В типичном UI-коде достаточно Object.keys / entries.


Практика: валидация и patch #

Проверка обязательных полей в теле запроса:

const required = ['event', 'data', 'meta']
const apiPayload = {
  event: 'cart.updated',
  data: { cartId: 'cart-7a2' },
  meta: { sentAt: '2026-07-14T12:00:00Z' },
}

const missing = required.filter((key) => !Object.hasOwn(apiPayload, key))
// []

Иммутабельное обновление по белому списку ключей:

const allowed = ['displayName', 'avatarUrl']
const patch = { displayName: 'Мария К.', role: 'admin', avatarUrl: null }

const safePatch = Object.fromEntries(
  Object.entries(patch).filter(([key]) => allowed.includes(key))
)
// { displayName: 'Мария К.', avatarUrl: null }

Связь с копированием объектов: fromEntries(entries) — обратная операция к разбору объекта.


for…in #

Цикл for...in обходит перечисляемые ключи по цепочке прототипов, если не ограничить hasOwn:

for (const key in playlist) {
  if (Object.hasOwn(playlist, key)) {
    console.log(key, playlist[key])
  }
}

Для собственных ключей данных предпочтительнее Object.keys или for...of по entries — меньше сюрпризов от унаследованных полей.


Итог #

  • Object.keys, values, entries — собственные перечисляемые строковые ключи.
  • Symbol — уникальный ключ или метка; не виден в keys без отдельного API.
  • Object.hasOwn проверяет собственное свойство без обращения к прототипу.
  • Унаследованные и неперечисляемые поля требуют других методов или раздела ООП.
  • Для API и форм комбинируйте перечисление с белыми списками и валидацией.

См. также #

Часто спрашивают

Почему this «потерялся» в колбэке?
У обычной функции this зависит от способа вызова. Сохраните контекст через bind или используйте стрелочную функцию осознанно. Подробнее