Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Що таке патерн Proxy?». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Proxy** - це структурний патерн, де об'єкт-замінник контролює доступ до реального ресурсу. Зберігає той самий інтерфейс, але перехоплює кожен виклик для валідації, кешування або контролю доступу. ```javascript const proxy = new Proxy(target, { get(t, prop) { console.log('Читання', prop); return t[prop]; } }); ``` **Головне:** кожна операція спочатку проходить через проксі, не через реальний об'єкт.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Proxy** - це структурний патерн проектування, де об'єкт-замінник стоїть перед реальним ресурсом і контролює доступ до нього, додаючи поведінку на кшталт lazy loading, валідації або кешування, не змінюючи інтерфейс ресурсу. ## Теорія ### TL;DR - Аналогія: рецепшн готелю. Ти звертаєшся до проксі, воно контактує з реальним об'єктом тільки коли потрібно, і перевіряє права по дорозі - Той самий інтерфейс що і у реального об'єкта, але кожен виклик спочатку проходить через проксі - В JavaScript є нативний `Proxy` (ES6+) з trap-ами для `get`, `set`, `apply` та інших операцій - Використовуй коли реальний об'єкт дорогий у створенні, потребує контролю доступу або відстеження викликів - Пропускай коли немає додаткової логіки - прямий доступ простіший ### Швидкий приклад ```javascript const target = { name: 'Іван', age: 25 }; const handler = { get(target, prop) { if (prop === 'age' && target[prop] < 0) { throw new Error('Невалідний вік'); } return target[prop]; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // "Іван" console.log(proxy.age); // 25 target.age = -5; console.log(proxy.age); // Error: Невалідний вік ``` Проксі перехоплює кожне читання властивості. Оригінальний `target` не змінюється - `proxy.age` кинув помилку, але `target.age` залишився -5. ### Головна ідея Proxy надає точно такий самий інтерфейс як і реальний об'єкт. Клієнтський код нічого не помічає. Кожна операція спочатку проходить через проксі, і там живе вся додаткова логіка. Це відокремлює контроль доступу від самого об'єкта. Proxy і Decorator обидва обгортають об'єкти, але мета різна. Proxy контролює доступ до одного конкретного об'єкта: lazy loading, кешування, авторизація. Decorator додає поведінку накладанням обгорток, часто на багато об'єктів одразу. Структура схожа, цілі різні. ### Коли використовувати - Дорогий ресурс (запит до БД, завантаження зображення): проксі для lazy loading або кешування, щоб реальна робота відбувалась один раз - Контроль доступу (поля тільки для адміна, API ключі): проксі для перевірки прав перед поверненням даних - Віддалений або важкий об'єкт: проксі як легкий локальний замінник - Rate limiting: проксі для обмеження кількості викликів до зовнішнього API - Відстеження викликів: проксі для логування читань і записів без зміни оригіналу Уникай Proxy коли немає додаткової поведінки. Overhead невеликий, але реальний. ### Як Proxy працює в JavaScript JavaScript's `Proxy` (ES6+) використовує meta-programming хуки V8 для перехоплення операцій. Коли ти читаєш `proxy.prop`, V8 спочатку перевіряє `get` trap у твоєму handler, виконує твій код, потім делегує до target якщо ти повернув нормально. Трапи `set` і `apply` перехоплюють записи в властивості та виклики функцій. Node.js і браузери дотримуються одної специфікації. `Reflect` - безпечний спосіб делегування всередині трапу. Використовуй `Reflect.get(target, prop)` замість `target[prop]` щоб уникнути нескінченної рекурсії, якщо target сам є `Proxy`. ### Типові помилки **1. Пряма мутація target обходить проксі.** ```javascript const proxy = new Proxy( { count: 0 }, { set(t, p, v) { console.log('Set!'); t[p] = v; return true; } } ); proxy.count++; // Логує "Set!" target.count++; // Нічого не логується - проксі обійдено ``` Проксі перехоплює тільки доступ через себе. На практиці, щоразу коли команда відкриває доступ і до проксі, і до raw target, проксі стає марним за кілька днів. Відкривай тільки проксі, ніколи сирий об'єкт. **2. Забутий `return true` у `set` трапі.** ```javascript const handler = { set(t, p, v) { t[p] = v; } // Бракує return true }; const p = new Proxy({}, handler); p.x = 1; // Тихий збій у non-strict, TypeError у strict mode ``` `set` trap мусить повертати `true` за специфікацією. Завжди додавай `return true` після присвоєння. **3. Нескінченна рекурсія всередині трапів.** ```javascript const handler = { get(t, p) { return t[p]; } // Зациклиться якщо t теж є Proxy }; ``` Використовуй `Reflect.get(target, prop)` щоб не викликати той самий трап знову. **4. Припущення що всі операції перехоплюються.** Немає `delete` trap за замовчуванням. `delete proxy.x` кидає `TypeError` у strict mode якщо не додати `deleteProperty` trap. Перевіряй які трапи існують перед тим як вважати що операція перехоплена. ### Де застосовується - Vue 3: проксі виявляють зміни вглиб об'єкта і автоматично ініціюють перерендер - Apollo Client: кешування на рівні полів, яке проксіює GraphQL резолвери - React DevTools: проксіювання стану компонентів для інспекції без мутацій - Express: proxy обгортки навколо DB викликів для lazy loading - Proxyquire: мокінг залежностей модулів у Node.js тестах ### Питання на співбесіді **Q:** Яка різниця між Proxy і Decorator? **A:** Proxy контролює доступ до одного конкретного об'єкта (lazy loading, кешування, авторизація). Decorator додає поведінку накладаючи обгортки і застосовується до багатьох об'єктів. Структура схожа, але ціль різна. **Q:** Реалізуй logging proxy для довільної функції. **A:** ```javascript const loggingProxy = (fn) => new Proxy(fn, { apply(target, thisArg, args) { console.log(`Виклик ${target.name} з`, args); return target.apply(thisArg, args); } }); ``` **Q:** Як Proxy впливає на продуктивність порівняно з прямим доступом? **A:** Невеликий overhead, близько 1-5% за бенчмарками V8. Для I/O-bound операцій ця різниця непомітна. Уникай Proxy у гарячих внутрішніх циклах де overhead накопичується. **Q:** Яка різниця між virtual proxy і protection proxy? **A:** Virtual proxy відкладає дороге створення об'єкта до моменту коли він дійсно потрібен, наприклад lazy loading великого зображення. Protection proxy обмежує доступ на основі прав, наприклад перевірка прав адміна перед поверненням чутливих даних. **Q:** Як синхронізувати стан проксі між процесами у Node.js cluster? **A:** Ніяк напряму. Proxy - in-memory об'єкти, вони не серіалізуються між процесами. Для спільного кешу використовуй Redis або message-passing систему. ## Приклади ### Базовий: нативний Proxy з валідацією ```javascript const user = { name: 'Іван', age: 25 }; const handler = { get(target, prop) { if (prop === 'age' && target[prop] < 0) { throw new Error('Невалідний вік'); } return target[prop]; }, set(target, prop, value) { if (prop === 'age' && typeof value !== 'number') { throw new TypeError('Вік має бути числом'); } target[prop] = value; return true; // обов'язково за специфікацією Proxy } }; const proxy = new Proxy(user, handler); console.log(proxy.name); // "Іван" proxy.age = 31; // все добре proxy.age = 'тридцять'; // TypeError: Вік має бути числом ``` Обидва трапи `get` і `set` активні. Читання `proxy.name` проходить через `get`, запис `proxy.age` рядком падає у `set`. Оригінальний `user` ніколи не змінюється напряму. ### Середній: кешуючий Proxy для API викликів ```javascript class ApiProxy { constructor(api) { this.api = api; this.cache = new Map(); } async getUser(id) { if (!this.cache.has(id)) { console.log('Запит до API...'); this.cache.set(id, await this.api.fetchUser(id)); } return this.cache.get(id); } } const realApi = { async fetchUser(id) { return { id, name: 'Користувач' + id }; } }; const proxy = new ApiProxy(realApi); console.log(await proxy.getUser(1)); // Запит до API... // { id: 1, name: 'Користувач1' } console.log(await proxy.getUser(1)); // { id: 1, name: 'Користувач1' } (з кешу, без запиту) ``` Реальний API викликається один раз для кожного `id`. Далі проксі повертає з внутрішнього `Map`. Apollo Client використовує подібний підхід для кешування GraphQL запитів. ### Просунутий: logging proxy для функцій ```javascript const loggingProxy = (fn) => new Proxy(fn, { apply(target, thisArg, args) { console.log(`Виклик ${target.name} з`, args); const result = target.apply(thisArg, args); console.log('Результат:', result); return result; } }); function calculateTotal(price, tax) { return price + price * tax; } const logged = loggingProxy(calculateTotal); logged(100, 0.2); // Виклик calculateTotal з [100, 0.2] // Результат: 120 ``` `apply` trap спрацьовує при кожному виклику функції. Це дає логування, вимірювання часу або валідацію аргументів без змін у `calculateTotal`. Той самий патерн працює для будь-якої функції.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.