Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Об'єкт Proxy». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`Proxy`, це обгортка над об'єктом, яка перехоплює операції над ним: читання і запис властивостей, перевірку `in`, видалення, перебір ключів, виклик функції та `new`.** Створюється як `new Proxy(target, handler)`, де `target`, це вихідний об'єкт, а `handler`, це об'єкт із пастками (traps): `get`, `set`, `has`, `deleteProperty`, `ownKeys`, `apply`, `construct` та іншими. Усередині пастки ви можете підмінити результат, кинути помилку, залогувати доступ або віддати значення за замовчуванням, а щоб виконати стандартну поведінку, викликаєте відповідний метод `Reflect`. Саме на `Proxy` побудована реактивність Vue 3, а також валідація даних, захист властивостей і динамічні API-обгортки. ```javascript const user = new Proxy({}, { set(target, prop, value) { if (prop === 'age' && typeof value !== 'number') { throw new TypeError('Вік має бути числом'); } target[prop] = value; return true; // без true буде TypeError у strict mode } }); user.age = 30; // ок ``` **Ключове:** `Proxy` стає між кодом і об'єктом та дає перевизначити будь-яку базову операцію, а `Reflect` повертає до стандартної поведінки.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`Proxy`, це обгортка над об'єктом, яка дозволяє перехоплювати (intercept) операції над ним.** Простіше кажучи, `Proxy` дає можливість стати між вашим кодом і об'єктом, щоб контролювати, що відбувається під час звернення до властивостей, їх зміни та інших дій. ## Теорія ### TL;DR - `new Proxy(target, handler)` створює обгортку над вихідним об'єктом. - `handler`, це об'єкт із пастками (traps), кожна з яких перехоплює одну операцію. - Основні пастки: `get`, `set`, `has`, `deleteProperty`, `ownKeys`, `apply`, `construct`. - Пастки `set` і `deleteProperty` мають повертати `true`, інакше в strict mode буде `TypeError`. - `Reflect` містить методи, що відповідають кожній пастці, і виконує поведінку за замовчуванням. - Типові застосування: валідація, логування, значення за замовчуванням, захист властивостей, реактивність Vue 3, i18n з fallback. ### Швидкий приклад ```javascript const user = { name: 'Tim', age: 25 }; const proxy = new Proxy(user, { get(target, prop) { console.log(`Читання властивості "${prop}"`); return target[prop]; }, set(target, prop, value) { console.log(`Зміна "${prop}" на "${value}"`); target[prop] = value; return true; // обов'язково повернути true, щоб не було помилки } }); console.log(proxy.name); // Читання властивості "name" -> "Tim" proxy.age = 26; // Зміна "age" на "26" ``` ### Синтаксис ```javascript const proxy = new Proxy(target, handler); ``` | Параметр | Опис | | --- | --- | | `target` | вихідний об'єкт, над яким будується проксі | | `handler` | об'єкт із пастками (traps), що перехоплюють операції | Проксі не копіює об'єкт: усі операції в підсумку доходять до `target`, якщо пастка сама їх туди пропустить. Якщо пастки для операції немає, вона виконується так, ніби `Proxy` не існує. ### Практичні приклади пасток **Валідація під час запису.** Пастка `set` перевіряє тип і не дає покласти в об'єкт сміття. ```javascript const user = new Proxy({}, { set(target, prop, value) { if (prop === 'age' && typeof value !== 'number') { throw new TypeError('Вік має бути числом'); } target[prop] = value; return true; } }); user.age = 30; // ок user.age = 'тридцять'; // TypeError ``` **Значення за замовчуванням.** Пастка `get` віддає підставне значення для властивостей, яких немає. ```javascript const config = new Proxy({}, { get(target, prop) { return prop in target ? target[prop] : '<не задано>'; } }); console.log(config.host); // <не задано> config.host = 'localhost'; console.log(config.host); // localhost ``` **Захист від видалення.** Пастка `deleteProperty` забороняє прибрати ключове поле. ```javascript const secure = new Proxy({ id: 1, name: 'Admin' }, { deleteProperty(target, prop) { if (prop === 'id') { console.log('Видалення заборонено!'); return false; } delete target[prop]; return true; } }); delete secure.name; // видалиться delete secure.id; // Видалення заборонено! ``` ### Reflect і поведінка за замовчуванням `Reflect`, це набір методів, що відповідають усім пасткам `Proxy`. Він дозволяє викликати **поведінку за замовчуванням**, коли вона вам потрібна. ```javascript function createLoggedObject(obj) { return new Proxy(obj, { get(target, prop, receiver) { console.log(`Доступ до властивості "${prop}"`); return Reflect.get(target, prop, receiver); // стандартна поведінка } }); } const user = createLoggedObject({ name: 'Tim' }); console.log(user.name); // Доступ до властивості "name" ``` Якщо не використовувати `Reflect`, довелося б вручну писати `target[prop]`, а це може зламати поведінку успадкування і `this`: геттер у прототипі отримає неправильний `receiver` і читатиме поля не з проксі, а з `target`. ### Доступні пастки (traps) | Пастка | Що перехоплює | Аналог Reflect | | --- | --- | --- | | `get` | Читання властивості | `Reflect.get()` | | `set` | Запис властивості | `Reflect.set()` | | `has` | Перевірку `prop in obj` | `Reflect.has()` | | `deleteProperty` | Видалення `delete obj[prop]` | `Reflect.deleteProperty()` | | `ownKeys` | Отримання всіх ключів (`Object.keys`, `for...in`) | `Reflect.ownKeys()` | | `getOwnPropertyDescriptor` | Дескриптор властивості | `Reflect.getOwnPropertyDescriptor()` | | `defineProperty` | Створення або зміну властивості | `Reflect.defineProperty()` | | `preventExtensions` | Заборону додавати нові властивості | `Reflect.preventExtensions()` | | `isExtensible` | Перевірку можливості розширення | `Reflect.isExtensible()` | | `getPrototypeOf` | Отримання прототипу | `Reflect.getPrototypeOf()` | | `setPrototypeOf` | Зміну прототипу | `Reflect.setPrototypeOf()` | | `apply` | Виклик функції | `Reflect.apply()` | | `construct` | Виклик через `new` | `Reflect.construct()` | Проксіювати можна не лише звичайний об'єкт, а й функцію, і тоді працюють пастки `apply` та `construct`: ```javascript function sum(a, b) { return a + b; } const proxySum = new Proxy(sum, { apply(target, thisArg, args) { console.log(`Виклик функції з аргументами: ${args}`); return Reflect.apply(target, thisArg, args); } }); console.log(proxySum(2, 3)); // Виклик функції з аргументами: 2,3 -> 5 ``` ### Де Proxy справді корисний | Сценарій | Приклад використання | | --- | --- | | **Валідація даних** | Перевірка типів під час запису | | **Налагодження і логування** | Перехоплення `get` та `set` для аналізу | | **Реактивність (Vue 3)** | Відстеження змін властивостей | | **Захист і обмеження** | Заборона видаляти чи змінювати властивості | | **Локалізація і доступ за ключем** | i18n, fallback-дані | | **API-обгортки** | «Розумні» динамічні об'єкти, наприклад REST API на льоту | Приклад динамічної структури для i18n: замість помилки або `undefined` користувач бачить сам ключ, а перекладачі одразу помічають, чого бракує. ```javascript const dictionary = { hello: 'Привіт', world: 'світ' }; const i18n = new Proxy(dictionary, { get(target, key) { if (key in target) return target[key]; return `[${key}]`; // підставимо ключ замість перекладу } }); console.log(i18n.hello); // Привіт console.log(i18n.missingKey); // [missingKey] ``` Короткий підсумок термінів: | Термін | Опис | | --- | --- | | `Proxy` | Об'єкт, що перехоплює операції з іншим об'єктом | | `target` | Вихідний об'єкт (ціль) | | `handler` | Об'єкт із пастками (методами-перехоплювачами) | | `trap` | Метод, що перехоплює певну операцію (`get`, `set` тощо) | | `Reflect` | Дозволяє викликати стандартну поведінку за замовчуванням | ### Типові помилки - **Забути `return true` в `set` або `deleteProperty`.** Пастка має повідомити рушію, що операція вдалася. У strict mode, а модулі завжди strict, повернення `false` або `undefined` призводить до `TypeError`. - **Писати `target[prop]` замість `Reflect.get(target, prop, receiver)`.** Втрачається `receiver`, тому геттери з прототипу отримають неправильний `this`. - **Вважати, що проксі змінює сам об'єкт.** Пастки спрацьовують тільки для звернень через проксі. Якщо десь залишилося посилання на `target`, усі перевірки обходяться. - **Порушувати інваріанти об'єкта.** Для неконфігурованої властивості пастка `get` не може повернути інше значення, а для нерозширюваного об'єкта `ownKeys` не може вигадати нові ключі: рушій кине `TypeError`. - **Ставити проксі на все підряд.** Кожна операція проходить через ваш код, тому проксі помітно повільніший за звичайний об'єкт у гарячому циклі. - **Очікувати, що `Proxy` можна поліфілити.** Перехоплення реалізовано на рівні рушія, тому для старих середовищ без підтримки замінника немає.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.