Пошук уроків, статей та іншого контенту
Унікальні ідентифікатори властивостей, перехоплення операцій над об'єктами та їхній стандартний API-компаньйон.
Ми вже бачили Symbol.iterator у двох попередніх уроках. Symbol — окремий примітивний тип (модуль «Змінні та типи даних» згадував сім примітивів — Symbol саме там), кожен виклик Symbol() створює абсолютно унікальне значення, навіть з однаковим описом:
const id1 = Symbol("id");
const id2 = Symbol("id");
id1 === id2; // false — різні унікальні символи, попри однаковий описSymbol як ключ властивості об'єкта гарантовано не конфліктує з жодним іншим ключем — ні написаним вами, ні доданим сторонньою бібліотекою пізніше. Це і використовує сама мова для Symbol.iterator: бібліотека може додати цей метод об'єкту, не боячись випадково перезаписати чи конфліктувати зі звичайною властивістю на кшталт iterator як рядка.
const ANALYTICS_ID = Symbol("analyticsId");
const user = {
name: "Олена",
[ANALYTICS_ID]: "usr_8f2a1", // внутрішній ідентифікатор, не видно при звичайному переборі
};
Object.keys(user); // ["name"] — символьні ключі не потрапляють у звичайний перебір
user[ANALYTICS_ID]; // "usr_8f2a1" — доступ можливий, якщо є посилання на сам символProxy — обгортка навколо об'єкта, що дозволяє перехоплювати й перевизначати базові операції: читання властивості, запис, перевірку наявності. Другий аргумент — об'єкт-«обробник» (handler) із функціями-пастками (traps):
const user = { name: "Максим", age: 30 };
const loggedUser = new Proxy(user, {
get(target, property) {
console.log(`Читання властивості: ${String(property)}`);
return target[property];
},
set(target, property, value) {
console.log(`Запис ${String(property)} = ${value}`);
target[property] = value;
return true;
},
});
loggedUser.name; // виводить "Читання властивості: name", повертає "Максим"
loggedUser.age = 31; // виводить "Запис age = 31"function createValidatedUser(initial) {
return new Proxy(initial, {
set(target, property, value) {
if (property === "age" && (typeof value !== "number" || value < 0)) {
throw new TypeError("age має бути невід'ємним числом");
}
target[property] = value;
return true;
},
});
}
const user = createValidatedUser({ name: "Ірина", age: 25 });
user.age = -5; // TypeError: age має бути невід'ємним числомReflect — вбудований об'єкт із методами, що дублюють базові операції над об'єктами (Reflect.get, Reflect.set тощо) у формі звичайних функцій. Усередині пасток Proxy його використовують замість прямого звернення до target, щоб гарантовано зберегти стандартну поведінку там, де не потрібна кастомна логіка:
const loggedUser = new Proxy(user, {
get(target, property, receiver) {
console.log(`Читання: ${String(property)}`);
return Reflect.get(target, property, receiver); // еквівалент target[property], але коректніше для складніших випадків (успадкування, геттери)
},
});Для більшості простих випадків target[property] і Reflect.get(target, property) дають однаковий результат — різниця стає важливою в складніших сценаріях з успадкуванням через прототипи (модуль «JavaScript під капотом»), де Reflect гарантує коректну поведінку this всередині гетерів.
Proxy й Reflect рідко потрібні в типовому прикладному коді — здебільшого їх використовують автори бібліотек (наприклад, реактивні системи стану на кшталт тих, що лежать в основі деяких фреймворків, автоматично відстежують читання/запис властивостей саме через Proxy). Розуміння механізму допомагає читати вихідний код таких бібліотек і розуміти, «як воно взагалі працює» під капотом.
Забутий return true в пастці set — Proxy у strict mode кине TypeError, якщо set не підтвердить успіх явним true.
Надмірне використання Symbol там, де досить звичайного рядкового ключа — виправдано лише коли потрібна гарантована відсутність конфліктів.
Створення Proxy для продуктивно критичного коду без потреби — кожна операція проходить через додатковий шар пасток, що має свою вартість.
Symbol створює гарантовано унікальні ключі властивостей, які не конфліктують з іншими — саме так влаштований Symbol.iterator із попередніх уроків. Proxy дозволяє перехоплювати фундаментальні операції над об'єктом (читання, запис, перевірку наявності), а Reflect — стандартний API-компаньйон для виконання тих самих операцій усередині пасток, не втрачаючи стандартної поведінки.
Спробуйте самостійно
Створіть Proxy навколо об'єкта конфігурації, який кидає помилку при спробі прочитати властивість, якої не існує (пастка get: якщо property відсутній у target, кинути ReferenceError замість повернення undefined).