Skip to main content

Symbol у JavaScript

Symbol, це примітивний тип даних, доданий в ES6, що представляє унікальне й незмінне значення. Кожен виклик Symbol() створює нове унікальне значення, навіть якщо опис той самий, тому символи використовують як ключі властивостей, що гарантовано ні з чим не конфліктують.

Теорія

TL;DR

  • Symbol() створює унікальне незмінне значення; два символи з однаковим описом не рівні.
  • Основне застосування, це унікальні ключі властивостей об'єкта без ризику конфлікту імен.
  • Символьні ключі не потрапляють у for...in, Object.keys() і JSON.stringify().
  • Дістати їх можна через Object.getOwnPropertySymbols(), тож це «приховані», а не приватні поля.
  • Well-known symbols (Symbol.iterator, Symbol.toPrimitive та інші) змінюють поведінку вбудованих операцій.
  • Symbol.for(key) бере символ із глобального реєстру, тому такі символи можна порівнювати між модулями.

Швидкий приклад

javascript
const id1 = Symbol('user'); const id2 = Symbol('user'); console.log(id1 === id2); // false const ID = Symbol('id'); const user = { name: 'Tim', [ID]: 1 }; console.log(user[ID]); // 1 console.log(Object.keys(user)); // ["name"]

Що таке Symbol і чому він унікальний

Символ створюється викликом функції:

javascript
const id = Symbol();

Кожен виклик Symbol() створює нове унікальне значення, навіть якщо описи збігаються:

javascript
const id1 = Symbol('user'); const id2 = Symbol('user'); console.log(id1 === id2); // false

Хоча обидва символи мають однаковий опис 'user', вони різні. Опис, це лише підказка для налагодження, він не впливає на рівність.

Навіщо потрібен Symbol

Символи найчастіше використовують як унікальні ключі властивостей об'єкта, щоб уникнути конфліктів імен.

Звичайні ключі конфліктують:

javascript
const user = { name: 'Tim' }; user.id = 1; user.id = 2; // перезаписали

А із символом такого не станеться:

javascript
const ID = Symbol('id'); const user = { name: 'Tim', [ID]: 1 }; console.log(user[ID]); // 1

Тепер властивість [ID]:

  • унікальна;
  • не видно під час звичайного перебору властивостей.

Символи не беруть участі у звичайному переборі

javascript
for (let key in user) { console.log(key); } // "name"
javascript
console.log(Object.keys(user)); // ["name"]

Символ не з'являється у for...in, Object.keys() та JSON.stringify(). Але отримати його все одно можна:

javascript
console.log(Object.getOwnPropertySymbols(user)); // [Symbol(id)]

Вбудовані (well-known) символи

JavaScript має вбудовані символи, спеціальні значення, які дозволяють змінювати поведінку вбудованих операцій.

СимволДля чого використовується
Symbol.iteratorробить об'єкт ітерабельним (for...of)
Symbol.toPrimitiveналаштовує перетворення до примітиву
Symbol.toStringTagзадає рядок, який повертає Object.prototype.toString
Symbol.hasInstanceперевизначає поведінку instanceof
Symbol.asyncIteratorдозволяє використовувати for await...of

Приклад із Symbol.iterator:

javascript
const arr = [1, 2, 3]; const iterator = arr[Symbol.iterator](); console.log(iterator.next()); // { value: 1, done: false }

Саме Symbol.iterator робить масив «ітерабельним», і саме завдяки цьому працюють for...of та spread (...arr).

Приклад із Symbol.toPrimitive:

javascript
const user = { name: 'Tim', age: 25, [Symbol.toPrimitive](hint) { return hint === 'string' ? this.name : this.age; } }; console.log(String(user)); // "Tim" console.log(+user); // 25

Символ дозволяє контролювати, як об'єкт перетворюється на число або рядок.

Глобальний реєстр і «приховані» ключі

Іноді потрібно, щоб символ з однаковим іменем був тим самим. Для цього є глобальний реєстр символів:

javascript
const id1 = Symbol.for('user'); const id2 = Symbol.for('user'); console.log(id1 === id2); // true

Symbol.for(key) шукає символ у глобальному реєстрі, і якщо не знаходить, створює новий та зберігає його під цим ключем. Зворотну операцію робить Symbol.keyFor():

javascript
Symbol.keyFor(id1); // 'user'

Символи добре підходять для:

  • створення «приватних» властивостей у бібліотеках і класах;
  • розширення чужих об'єктів без ризику зіпсувати їхні дані;
  • метаданих, наприклад у фреймворках чи ORM.

Приклад використання в класі:

javascript
const _id = Symbol('id'); class User { constructor(name) { this.name = name; this[_id] = Math.random(); } } const user = new User('Tim'); console.log(user); // { name: 'Tim', [Symbol(id)]: 0.123... } console.log(Object.keys(user)); // ["name"]

Символ робить поле _id невидимим під час звичайної ітерації і захищає його від випадкового перезапису.

Підсумкова таблиця:

ВластивістьОпис
ТипПримітив
Унікальністькожен Symbol() унікальний
Використовується якключ для властивостей об'єктів
Не видноу for...in, Object.keys(), JSON.stringify()
Глобальний реєстрчерез Symbol.for() та Symbol.keyFor()
Приклади вбудованих символівSymbol.iterator, Symbol.toPrimitive, Symbol.hasInstance та інші

Типові помилки

  • Очікувати, що Symbol('id') === Symbol('id'). Опис не робить символи рівними, кожен виклик дає новий символ.
  • Вважати символьні поля справді приватними. Їх видно через Object.getOwnPropertySymbols() і Reflect.ownKeys(); для справжньої приватності є #privateField.
  • Викликати Symbol через new. Це TypeError, бо Symbol, це примітив, а не конструктор.
  • Робити конкатенацію символу з рядком ('id: ' + sym). Неявне перетворення кидає TypeError, потрібен String(sym) або sym.description.
  • Плутати Symbol() і Symbol.for(). Перший завжди унікальний, другий шерить значення через глобальний реєстр.
  • Розраховувати, що символьні поля переживуть JSON.stringify() або structuredClone(). Вони просто зникнуть.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.