В Node.js библиотека http добавляет на объект запроса служебные свойства — но не хочет конфликтовать с полями которые создаёт разработчик. В React у каждого JSX-элемента есть внутренний тип $$typeof — Symbol, который нельзя случайно перезаписать из пользовательского кода. Symbol — это гарантированно уникальный ключ.
Когда нужно добавить свойство к чужому объекту (или к объекту из внешней библиотеки), строковые ключи могут конфликтовать. Symbol гарантирует уникальность: два Symbol с одинаковым описанием всё равно разные.
const id = Symbol('id') // 'id' — описание только для отладки
const id2 = Symbol('id')
console.log(id === id2) // false — всегда уникальны!
console.log(typeof id) // 'symbol'
console.log(id.toString()) // 'Symbol(id)'
console.log(id.description) // 'id'Символьные ключи не перечисляются в for...in, не возвращаются Object.keys(), JSON.stringify(), Object.entries():
const TOKEN = Symbol('token')
const user = {
name: 'Иван',
[TOKEN]: 'eyJhbGciOiJIUzI1NiJ9...' // вычисляемый ключ с Symbol
}
console.log(user.name) // 'Иван'
console.log(user[TOKEN]) // 'eyJ...' — доступен если знаешь Symbol
for (const key in user) {
console.log(key) // только 'name' — TOKEN не виден!
}
Object.keys(user) // ['name']
JSON.stringify(user) // '{"name":"Иван"}' — Symbol исчез
// Но получить можно явно:
Object.getOwnPropertySymbols(user) // [Symbol(token)]Symbol.for(key) создаёт Symbol в глобальном реестре. Повторный вызов с тем же ключом возвращает тот же Symbol:
const s1 = Symbol.for('app:theme')
const s2 = Symbol.for('app:theme')
console.log(s1 === s2) // true — один и тот же Symbol!
Symbol.keyFor(s1) // 'app:theme'Используй Symbol.for когда один Symbol нужен в нескольких модулях.
Встроенные Symbol настраивают поведение объектов:
// Symbol.iterator — делает объект итерируемым
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from
return {
next: () => current <= this.to
? { value: current++, done: false }
: { done: true, value: undefined }
}
}
}
console.log([...range]) // [1, 2, 3, 4, 5]
for (const n of range) console.log(n) // 1 2 3 4 5const price = {
amount: 1500,
currency: 'RUB',
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.amount
if (hint === 'string') return this.amount + ' ' + this.currency
return this.amount
}
}
console.log(`Цена: ${price}`) // 'Цена: 1500 RUB'
console.log(+price) // 1500
console.log(price + 500) // 2000Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstanceОшибка 1: Symbol теряется — нет ссылки
const obj = {}
obj[Symbol('key')] = 'value' // Symbol создан inline — ссылки нет!
// Как получить это значение?
const syms = Object.getOwnPropertySymbols(obj) // [Symbol(key)]
obj[syms[0]] // 'value' — только так, через перебор
// Правильно — сохраняй Symbol в переменную
const KEY = Symbol('key')
obj[KEY] = 'value'
obj[KEY] // 'value' — всегда доступноОшибка 2: Symbol не конвертируется в строку автоматически
const sym = Symbol('test')
'Привет ' + sym // TypeError: Cannot convert a Symbol value to a string
// Правильно
'Привет ' + sym.toString() // 'Привет Symbol(test)'Ошибка 3: Symbol.for vs Symbol — путаница
Symbol('id') === Symbol('id') // false — всегда разные
Symbol.for('id') === Symbol.for('id') // true — один и тот же из реестра$$typeof = Symbol.for('react.element') защита от XSSSymbol.iterator, Symbol.asyncIterator, Symbol.toPrimitiveSymbol для уникальных скрытых ключей и Symbol.iterator на пользовательском объекте
// 1. Symbol как уникальный скрытый ключ — метаданные объекта
const _id = Symbol('id')
const _createdAt = Symbol('createdAt')
let counter = 0
function createEntity(data) {
return {
...data,
[_id]: 'entity-' + (++counter),
[_createdAt]: new Date().toISOString(),
}
}
const user = createEntity({ name: 'Алиса', email: 'alice@mail.ru' })
const order = createEntity({ product: 'Ноутбук', price: 50000 })
console.log(user.name) // 'Алиса'
console.log(user[_id]) // 'entity-1' — только если знаешь Symbol
console.log(order[_id]) // 'entity-2'
console.log(Object.keys(user)) // ['name', 'email'] — ID скрыты!
// Проверяем что Symbol не попадает в JSON
console.log(JSON.stringify(user)) // '{"name":"Алиса","email":"alice@mail.ru"}'
// 2. Symbol.iterator — кастомная пагинация
const pagedData = {
pages: [
['Страница 1, элемент 1', 'Страница 1, элемент 2'],
['Страница 2, элемент 1'],
['Страница 3, элемент 1', 'Страница 3, элемент 2', 'Страница 3, элемент 3'],
],
[Symbol.iterator]() {
let pageIdx = 0
let itemIdx = 0
const pages = this.pages
return {
next() {
if (pageIdx >= pages.length) return { done: true, value: undefined }
const item = pages[pageIdx][itemIdx]
itemIdx++
if (itemIdx >= pages[pageIdx].length) { pageIdx++; itemIdx = 0 }
return { value: item, done: false }
}
}
}
}
const allItems = [...pagedData]
console.log(allItems.length) // 6 — все элементы со всех страниц
console.log(allItems[0]) // 'Страница 1, элемент 1'В Node.js библиотека http добавляет на объект запроса служебные свойства — но не хочет конфликтовать с полями которые создаёт разработчик. В React у каждого JSX-элемента есть внутренний тип $$typeof — Symbol, который нельзя случайно перезаписать из пользовательского кода. Symbol — это гарантированно уникальный ключ.
Когда нужно добавить свойство к чужому объекту (или к объекту из внешней библиотеки), строковые ключи могут конфликтовать. Symbol гарантирует уникальность: два Symbol с одинаковым описанием всё равно разные.
const id = Symbol('id') // 'id' — описание только для отладки
const id2 = Symbol('id')
console.log(id === id2) // false — всегда уникальны!
console.log(typeof id) // 'symbol'
console.log(id.toString()) // 'Symbol(id)'
console.log(id.description) // 'id'Символьные ключи не перечисляются в for...in, не возвращаются Object.keys(), JSON.stringify(), Object.entries():
const TOKEN = Symbol('token')
const user = {
name: 'Иван',
[TOKEN]: 'eyJhbGciOiJIUzI1NiJ9...' // вычисляемый ключ с Symbol
}
console.log(user.name) // 'Иван'
console.log(user[TOKEN]) // 'eyJ...' — доступен если знаешь Symbol
for (const key in user) {
console.log(key) // только 'name' — TOKEN не виден!
}
Object.keys(user) // ['name']
JSON.stringify(user) // '{"name":"Иван"}' — Symbol исчез
// Но получить можно явно:
Object.getOwnPropertySymbols(user) // [Symbol(token)]Symbol.for(key) создаёт Symbol в глобальном реестре. Повторный вызов с тем же ключом возвращает тот же Symbol:
const s1 = Symbol.for('app:theme')
const s2 = Symbol.for('app:theme')
console.log(s1 === s2) // true — один и тот же Symbol!
Symbol.keyFor(s1) // 'app:theme'Используй Symbol.for когда один Symbol нужен в нескольких модулях.
Встроенные Symbol настраивают поведение объектов:
// Symbol.iterator — делает объект итерируемым
const range = {
from: 1, to: 5,
[Symbol.iterator]() {
let current = this.from
return {
next: () => current <= this.to
? { value: current++, done: false }
: { done: true, value: undefined }
}
}
}
console.log([...range]) // [1, 2, 3, 4, 5]
for (const n of range) console.log(n) // 1 2 3 4 5const price = {
amount: 1500,
currency: 'RUB',
[Symbol.toPrimitive](hint) {
if (hint === 'number') return this.amount
if (hint === 'string') return this.amount + ' ' + this.currency
return this.amount
}
}
console.log(`Цена: ${price}`) // 'Цена: 1500 RUB'
console.log(+price) // 1500
console.log(price + 500) // 2000Symbol.iterator, Symbol.toPrimitive, Symbol.hasInstanceОшибка 1: Symbol теряется — нет ссылки
const obj = {}
obj[Symbol('key')] = 'value' // Symbol создан inline — ссылки нет!
// Как получить это значение?
const syms = Object.getOwnPropertySymbols(obj) // [Symbol(key)]
obj[syms[0]] // 'value' — только так, через перебор
// Правильно — сохраняй Symbol в переменную
const KEY = Symbol('key')
obj[KEY] = 'value'
obj[KEY] // 'value' — всегда доступноОшибка 2: Symbol не конвертируется в строку автоматически
const sym = Symbol('test')
'Привет ' + sym // TypeError: Cannot convert a Symbol value to a string
// Правильно
'Привет ' + sym.toString() // 'Привет Symbol(test)'Ошибка 3: Symbol.for vs Symbol — путаница
Symbol('id') === Symbol('id') // false — всегда разные
Symbol.for('id') === Symbol.for('id') // true — один и тот же из реестра$$typeof = Symbol.for('react.element') защита от XSSSymbol.iterator, Symbol.asyncIterator, Symbol.toPrimitiveSymbol для уникальных скрытых ключей и Symbol.iterator на пользовательском объекте
// 1. Symbol как уникальный скрытый ключ — метаданные объекта
const _id = Symbol('id')
const _createdAt = Symbol('createdAt')
let counter = 0
function createEntity(data) {
return {
...data,
[_id]: 'entity-' + (++counter),
[_createdAt]: new Date().toISOString(),
}
}
const user = createEntity({ name: 'Алиса', email: 'alice@mail.ru' })
const order = createEntity({ product: 'Ноутбук', price: 50000 })
console.log(user.name) // 'Алиса'
console.log(user[_id]) // 'entity-1' — только если знаешь Symbol
console.log(order[_id]) // 'entity-2'
console.log(Object.keys(user)) // ['name', 'email'] — ID скрыты!
// Проверяем что Symbol не попадает в JSON
console.log(JSON.stringify(user)) // '{"name":"Алиса","email":"alice@mail.ru"}'
// 2. Symbol.iterator — кастомная пагинация
const pagedData = {
pages: [
['Страница 1, элемент 1', 'Страница 1, элемент 2'],
['Страница 2, элемент 1'],
['Страница 3, элемент 1', 'Страница 3, элемент 2', 'Страница 3, элемент 3'],
],
[Symbol.iterator]() {
let pageIdx = 0
let itemIdx = 0
const pages = this.pages
return {
next() {
if (pageIdx >= pages.length) return { done: true, value: undefined }
const item = pages[pageIdx][itemIdx]
itemIdx++
if (itemIdx >= pages[pageIdx].length) { pageIdx++; itemIdx = 0 }
return { value: item, done: false }
}
}
}
}
const allItems = [...pagedData]
console.log(allItems.length) // 6 — все элементы со всех страниц
console.log(allItems[0]) // 'Страница 1, элемент 1'Создай модуль для работы с сущностями с символьными метаданными. Реализуй: функцию createWithId(obj) — добавляет скрытый Symbol-ключ с уникальным ID; функцию getId(obj) — возвращает ID; функцию hasId(obj) — проверяет наличие ID. Symbol должен быть создан один раз и недоступен снаружи (замыкание).
createWithId: { ...obj, [idKey]: ++_counter }. getId: return obj[idKey]. hasId: return idKey in obj. Symbol создан вне функций — он общий для всех трёх функций, но снаружи модуля его не видно.