Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Symbol у JavaScript». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`Symbol`, це примітивний тип з ES6, кожне значення якого унікальне й незмінне, навіть якщо описи збігаються.** Його головне застосування, це унікальні ключі властивостей об'єкта: такий ключ гарантовано ні з чим не конфліктує і не з'являється у `for...in`, `Object.keys()` та `JSON.stringify()`, тож підходить для «прихованих» службових полів і метаданих. Окремо існують вбудовані well-known symbols (`Symbol.iterator`, `Symbol.toPrimitive` та інші), які дозволяють змінювати поведінку стандартних операцій мови. ```javascript const id1 = Symbol('user'); const id2 = Symbol('user'); console.log(id1 === id2); // false, кожен Symbol унікальний const ID = Symbol('id'); const user = { name: 'Tim', [ID]: 1 }; console.log(Object.keys(user)); // ["name"] console.log(user[ID]); // 1 ``` **Ключове:** `Symbol()` завжди дає нове унікальне значення, а `Symbol.for()` бере спільне з глобального реєстру.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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()`. Вони просто зникнуть.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.