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)бере символ із глобального реєстру, тому такі символи можна порівнювати між модулями.
Швидкий приклад
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 і чому він унікальний
Символ створюється викликом функції:
const id = Symbol();Кожен виклик Symbol() створює нове унікальне значення, навіть якщо описи збігаються:
const id1 = Symbol('user');
const id2 = Symbol('user');
console.log(id1 === id2); // falseХоча обидва символи мають однаковий опис 'user', вони різні. Опис, це лише підказка для налагодження, він не впливає на рівність.
Навіщо потрібен Symbol
Символи найчастіше використовують як унікальні ключі властивостей об'єкта, щоб уникнути конфліктів імен.
Звичайні ключі конфліктують:
const user = { name: 'Tim' };
user.id = 1;
user.id = 2; // перезаписалиА із символом такого не станеться:
const ID = Symbol('id');
const user = {
name: 'Tim',
[ID]: 1
};
console.log(user[ID]); // 1Тепер властивість [ID]:
- унікальна;
- не видно під час звичайного перебору властивостей.
Символи не беруть участі у звичайному переборі
for (let key in user) {
console.log(key);
}
// "name"console.log(Object.keys(user)); // ["name"]Символ не з'являється у for...in, Object.keys() та JSON.stringify(). Але отримати його все одно можна:
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:
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:
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Символ дозволяє контролювати, як об'єкт перетворюється на число або рядок.
Глобальний реєстр і «приховані» ключі
Іноді потрібно, щоб символ з однаковим іменем був тим самим. Для цього є глобальний реєстр символів:
const id1 = Symbol.for('user');
const id2 = Symbol.for('user');
console.log(id1 === id2); // trueSymbol.for(key) шукає символ у глобальному реєстрі, і якщо не знаходить, створює новий та зберігає його під цим ключем. Зворотну операцію робить Symbol.keyFor():
Symbol.keyFor(id1); // 'user'Символи добре підходять для:
- створення «приватних» властивостей у бібліотеках і класах;
- розширення чужих об'єктів без ризику зіпсувати їхні дані;
- метаданих, наприклад у фреймворках чи ORM.
Приклад використання в класі:
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(). Вони просто зникнуть.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.