Пошук уроків, статей та іншого контенту
Познайомтеся з типом Symbol, який дозволяє створювати унікальні значення. Розберіть його основне призначення та використання у властивостях об'єктів.
SymbolSymbol — це примітивний тип даних у JavaScript, призначений для створення унікальних значень.
Створити символ можна за допомогою функції-конструктора Symbol():
const firstId = Symbol();
const secondId = Symbol();
console.log(firstId === secondId); // falseКожен виклик Symbol() створює нове унікальне значення, навіть якщо передати однаковий опис:
const first = Symbol("id");
const second = Symbol("id");
console.log(first === second); // falseРядок "id" у цьому прикладі є лише описом символу. Він допомагає під час налагодження, але не впливає на унікальність.
const userId = Symbol("user id");
console.log(userId);
// Symbol(user id)Symbol є примітивним типом, як string, number або boolean.
Основне практичне застосування символів — створення властивостей об'єктів, які не конфліктують із властивостями, названими рядками.
const internalId = Symbol("internal id");
const user = {
name: "Anna",
[internalId]: 42
};
console.log(user.name); // Anna
console.log(user[internalId]); // 42Квадратні дужки необхідні, щоб JavaScript обчислив значення internalId і використав сам символ як ключ:
const key = Symbol("status");
const object = {
[key]: "active"
};
console.log(object[key]); // activeЯкщо не використати квадратні дужки, ключем стане звичайний текст key:
const key = Symbol("status");
const object = {
key: "active"
};
console.log(object[key]); // undefined
console.log(object.key); // activeСимвол можна використовувати і для додавання властивості після створення об'єкта:
const secretKey = Symbol("secret");
const settings = {};
settings[secretKey] = "hidden value";
console.log(settings[secretKey]); // hidden valueУявімо, що різні частини програми працюють з одним об'єктом. Кожна з них може додати власну властивість:
const frameworkKey = Symbol("framework data");
const pluginKey = Symbol("plugin data");
const element = {};
element[frameworkKey] = {
rendered: true
};
element[pluginKey] = {
version: "1.0"
};
console.log(element[frameworkKey]);
console.log(element[pluginKey]);Навіть якщо обидва розробники використають однаковий опис, властивості не перезапишуть одна одну:
const firstKey = Symbol("data");
const secondKey = Symbol("data");
const object = {
[firstKey]: "first value",
[secondKey]: "second value"
};
console.log(object[firstKey]); // first value
console.log(object[secondKey]); // second valueЦе корисно для:
внутрішніх властивостей бібліотек;
метаданих об'єктів;
уникнення конфліктів між розширеннями;
реалізації спеціальної поведінки об'єктів.
Символічні властивості не повертаються деякими стандартними способами перебору.
Наприклад, Object.keys() повертає лише перелік властивостей із рядковими ключами:
const id = Symbol("id");
const user = {
name: "Anna",
[id]: 42
};
console.log(Object.keys(user)); // ["name"]Цикл for...in також не перебирає властивості із символічними ключами:
for (const key in user) {
console.log(key); // name
}Щоб отримати символічні властивості, використовуйте Object.getOwnPropertySymbols():
const symbols = Object.getOwnPropertySymbols(user);
console.log(symbols); // [Symbol(id)]
console.log(user[symbols[0]]); // 42Якщо потрібно отримати і рядкові, і символічні ключі, можна використати Reflect.ownKeys():
console.log(Reflect.ownKeys(user));
// ["name", Symbol(id)]Метод Object.getOwnPropertyNames() повертає власні рядкові ключі, але не повертає символи.
Приклад функції, яка виводить усі ключі об'єкта:
const secret = Symbol("secret");
const account = {
username: "anna",
[secret]: "private data"
};
for (const key of Reflect.ownKeys(account)) {
console.log(key, account[key]);
}Результат буде приблизно таким:
username anna
Symbol(secret) private dataПорядок ключів у JavaScript має правила, але для звичайного використання важливо пам'ятати: Reflect.ownKeys() дозволяє побачити всі власні ключі об'єкта.
Object.getOwnPropertySymbolsObject.getOwnPropertySymbols() повертає лише символічні ключі безпосередньо самого об'єкта:
const first = Symbol("first");
const second = Symbol("second");
const object = {
[first]: 10,
[second]: 20,
name: "example"
};
const symbolKeys = Object.getOwnPropertySymbols(object);
for (const key of symbolKeys) {
console.log(key.description, object[key]);
}Властивість description містить опис символу:
const token = Symbol("access token");
console.log(token.description); // access tokenОпис не є самим значенням символу і не робить символи однаковими.
Символічні властивості зберігаються під час використання деяких операцій копіювання.
const id = Symbol("id");
const original = {
name: "Anna",
[id]: 42
};
const copy = { ...original };
console.log(copy[id]); // 42Object.assignconst id = Symbol("id");
const original = {
[id]: 42
};
const copy = Object.assign({}, original);
console.log(copy[id]); // 42Водночас JSON не підтримує символи:
const id = Symbol("id");
const user = {
name: "Anna",
[id]: 42
};
console.log(JSON.stringify(user)); // {"name":"Anna"}Символічна властивість ігнорується під час серіалізації в JSON.
Якщо символ передати як окреме значення до JSON.stringify, результатом буде undefined:
console.log(JSON.stringify(Symbol("value"))); // undefinedЗвичайний Symbol() завжди створює новий символ. Іноді потрібно отримати той самий символ у різних частинах програми. Для цього існує глобальний реєстр символів:
const first = Symbol.for("application.id");
const second = Symbol.for("application.id");
console.log(first === second); // trueSymbol.for(key):
шукає символ із таким ключем у глобальному реєстрі;
повертає наявний символ, якщо його вже створено;
створює новий символ, якщо такого ключа ще немає.
Щоб отримати ключ символу з глобального реєстру, використовуйте Symbol.keyFor():
const eventType = Symbol.for("user.created");
console.log(Symbol.keyFor(eventType)); // user.createdДля символу, створеного через звичайний Symbol(), Symbol.keyFor() поверне undefined:
const localSymbol = Symbol("local");
console.log(Symbol.keyFor(localSymbol)); // undefinedSymbol.for() варто використовувати лише тоді, коли символ має бути спільним між різними частинами програми. Для звичайних унікальних ключів достатньо Symbol().
Неявне перетворення символу на рядок заборонене:
const value = Symbol("id");
// console.log("Value: " + value); // TypeErrorЦе захищає від випадкового використання символу як тексту.
Для явного перетворення використовуйте String():
const value = Symbol("id");
console.log(String(value)); // Symbol(id)Також можна звернутися до властивості description:
const value = Symbol("id");
console.log(value.description); // idПеретворення символу на число неможливе:
const value = Symbol("id");
// Number(value); // TypeErrorJavaScript має вбудовані, або well-known, символи. Вони дозволяють змінювати стандартну поведінку об'єктів.
Наприклад, Symbol.iterator визначає, чи можна перебирати об'єкт у циклі for...of.
const collection = {
items: ["HTML", "CSS", "JavaScript"],
*[Symbol.iterator]() {
yield* this.items;
}
};
for (const item of collection) {
console.log(item);
}У цьому прикладі метод із ключем Symbol.iterator робить об'єкт доступним для for...of.
Інший приклад — Symbol.toPrimitive. Він визначає, як об'єкт перетворюється на примітивне значення:
const product = {
price: 100,
[Symbol.toPrimitive](hint) {
if (hint === "number") {
return this.price;
}
return `Product: ${this.price}`;
}
};
console.log(+product); // 100
console.log(String(product)); // Product: 100Найчастіше на практиці зустрічаються такі стандартні символи:
Symbol.iterator — поведінка під час ітерації;
Symbol.toPrimitive — перетворення об'єкта на примітив;
Symbol.toStringTag — результат Object.prototype.toString;
Symbol.hasInstance — поведінка оператора instanceof.
Символічні властивості іноді називають «прихованими», але це не справжня приватність. Їх можна знайти через Object.getOwnPropertySymbols() або Reflect.ownKeys().
const metadata = Symbol("metadata");
function createUser(name, role) {
return {
name,
[metadata]: {
role,
createdAt: new Date().toISOString()
}
};
}
function getMetadata(user) {
return user[metadata];
}
const user = createUser("Anna", "admin");
console.log(user.name); // Anna
console.log(getMetadata(user)); // об'єкт із метаданими
console.log(Object.keys(user)); // ["name"]Такий підхід підходить для уникнення випадкових конфліктів, але не для захисту секретних даних. Якщо властивість справді має бути недоступною ззовні, варто розглянути приватні поля класу з префіксом # або інші механізми контролю доступу.
const first = Symbol("key");
const second = Symbol("key");
console.log(first === second); // falseОпис символу не є його ідентифікатором. Для спільного символу використовуйте Symbol.for():
const first = Symbol.for("key");
const second = Symbol.for("key");
console.log(first === second); // trueconst key = Symbol("name");
const object = {
[key]: "Anna"
};
console.log(object.key); // undefined
console.log(object[key]); // AnnaКрапка завжди очікує буквальну назву властивості. Для символу потрібно використовувати квадратні дужки.
Object.keys() поверне символиconst key = Symbol("id");
const object = { [key]: 1 };
console.log(Object.keys(object)); // []Використовуйте Object.getOwnPropertySymbols() або Reflect.ownKeys().
const key = Symbol("id");
const object = { [key]: 1 };
console.log(JSON.stringify(object)); // "{}"Символічні властивості не потрапляють до JSON-результату. Якщо дані потрібно серіалізувати, використовуйте рядковий ключ або перетворіть дані в потрібний формат самостійно.
const key = Symbol("id");
// console.log("id: " + key); // TypeError
console.log(`id: ${String(key)}`); // id: Symbol(id)Неявне перетворення символу на рядок заборонене.
Symbol() створює унікальне примітивне значення.
Опис символу не впливає на його унікальність.
Символи можна використовувати як ключі властивостей об'єктів.
Для доступу до символічної властивості використовуються квадратні дужки.
Object.keys() і for...in не перебирають символічні властивості.
Object.getOwnPropertySymbols() повертає символічні ключі.
Reflect.ownKeys() повертає і рядкові, і символічні ключі.
Symbol.for() працює з глобальним реєстром символів.
Символи не серіалізуються у JSON.
Вбудовані символи, як-от Symbol.iterator, дають змогу змінювати стандартну поведінку об'єктів.
Символічні властивості зменшують ризик конфліктів, але не забезпечують справжню приватність.