Чому Reflect потрібен разом із Proxy?
Проблема до появи Reflect
До ES6 перехопити операції над об'єктом не можна було взагалі.
З появою Proxy це стало можливим - але одразу виникла інша проблема:
Як викликати "стандартну поведінку" після перехоплення?
Наприклад, ти хочеш логувати доступ до властивостей, але не ламати роботу об'єкта:
const user = { name: "Tim" };
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`Читання властивості "${prop}"`);
return target[prop]; // працює, але не завжди коректно
}
});Поки все окей, але якщо:
- властивість знаходиться в прототипі,
- або це геттер,
- або використовується наслідування,
то просте
target[prop]може поводитися інакше, ніж звичайнийobj.prop.
Рішення: Reflect
Щоб пастки Proxy не порушували поведінку JS, було введено Reflect.
Reflect надає функціональні аналоги всіх базових операцій мови, які Proxy може перехоплювати.
Наприклад:
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`Читання "${prop}"`);
// безпечний виклик стандартної поведінки:
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Запис "${prop}" = ${value}`);
return Reflect.set(target, prop, value, receiver);
}
});Тепер проксі поводиться точно як оригінальний об'єкт, тільки з додатковою логікою.
Чому не можна просто target[prop]
Ось приклад, де поведінка ламається без Reflect
const proto = {
get name() {
console.log("Геттер з прототипу");
return "Alex";
}
};
const obj = Object.create(proto);
const proxy = new Proxy(obj, {
get(target, prop) {
console.log("Перехоплення get");
return target[prop]; // геттер викликається з неправильним this
}
});
console.log(proxy.name);Результат:
Перехоплення get
Геттер з прототипуале всередині геттера this буде посилатися не на proxy, а на target.
Рішення з Reflect.get:
const proxy = new Proxy(obj, {
get(target, prop, receiver) {
console.log("Перехоплення get");
return Reflect.get(target, prop, receiver); // коректно
}
});
console.log(proxy.name);
// Перехоплення get
// Геттер з прототипуТепер this всередині геттера правильно вказує на proxy.
Взаємозв'язок 1 до 1
Кожна пастка Proxy має аналогічний метод Reflect, що виконує стандартну поведінку:
| Пастка Proxy | Метод Reflect | Що робить |
|---|---|---|
get | Reflect.get() | Отримання властивості |
set | Reflect.set() | Встановлення значення |
has | Reflect.has() | Перевірка prop in obj |
deleteProperty | Reflect.deleteProperty() | Видалення властивості |
ownKeys | Reflect.ownKeys() | Отримання всіх ключів |
getOwnPropertyDescriptor | Reflect.getOwnPropertyDescriptor() | Отримання дескриптора |
defineProperty | Reflect.defineProperty() | Визначення властивості |
getPrototypeOf | Reflect.getPrototypeOf() | Отримання прототипу |
setPrototypeOf | Reflect.setPrototypeOf() | Зміна прототипу |
isExtensible | Reflect.isExtensible() | Перевірка розширюваності |
preventExtensions | Reflect.preventExtensions() | Заборона розширення |
apply | Reflect.apply() | Виклик функції |
construct | Reflect.construct() | Виклик через new |
Таким чином, у кожній пастці Proxy можна просто додати свою логіку - і передати виконання стандартної операції Reflect'у.
Приклад: логування всіх операцій без ламання логіки
const user = { name: "Tim" };
const proxy = new Proxy(user, {
get(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`SET ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
deleteProperty(target, prop) {
console.log(`DELETE ${prop}`);
return Reflect.deleteProperty(target, prop);
}
});
proxy.name; // GET name
proxy.age = 30; // SET age = 30
delete proxy.age;// DELETE ageБез Reflect довелося б вручну імітувати поведінку мови (а це складно і небезпечно).
Додаткові переваги Reflect
- Методи повертають true/false, а не кидають помилки. Зручно для перевірки успішності операцій.
Reflect.defineProperty({}, 'x', { value: 1 }); // true- Можна програмно виконувати операції, які зазвичай виражаються операторами (
delete,in,new, виклик функції).
Reflect.apply(Math.max, null, [1, 2, 3]); // 3
Reflect.construct(Date, [2025, 0, 1]); // аналог new Date(2025, 0, 1)- Ти робиш
Proxy-> безпечно делегуєш черезReflect-> не ламаєш мову.
Підсумок
| Що | Роль |
|---|---|
Proxy | Перехоплює операції над об'єктом |
Reflect | Дозволяє виконати "оригінальну" операцію всередині пастки |
| Навіщо разом | Щоб розширювати чи модифікувати поведінку об'єкта, не ламаючи стандартну семантику JS |
| Перевага | Код передбачуваний, сумісний з наслідуванням, геттерами/сеттерами та вбудованими об'єктами |
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.