Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Пастки (traps) у Proxy». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Пастки (traps), це методи обробника (handler) об'єкта `Proxy`, які перехоплюють стандартні операції над об'єктом (читання, запис, видалення властивості, оператор `in`, перелік ключів, виклик функції, `new`) і дозволяють перевизначити їхню поведінку.** Кожна пастка відповідає одній внутрішній операції рушія і має ту саму сигнатуру, що й однойменний метод `Reflect`, тому всередині пастки стандартну поведінку викликають саме через `Reflect`. Якщо пастки для операції немає, вона виконується на `target` як зазвичай. ```javascript const proxy = new Proxy({ name: 'Maria' }, { get(target, prop, receiver) { console.log(`Reading ${String(prop)}`); return Reflect.get(target, prop, receiver); // стандартна поведінка } }); proxy.name; // Reading name, then "Maria" ``` **Ключове:** пастка, це функція-перехоплювач у `handler`, а `Reflect` всередині неї повертає операції її типову семантику.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Пастки (traps), це методи в обробнику об'єкта `Proxy`, які перехоплюють стандартні операції над об'єктом (читання, запис, видалення властивостей, виклик функції, оператор `in` тощо) і дозволяють перевизначити їхню поведінку.** Простіше кажучи, пастки дають змогу втрутитися в роботу JavaScript і змінити те, як об'єкт себе поводить. ## Теорія ### TL;DR - Пастки, це функції, які визначають у другому аргументі `Proxy`, у так званому обробнику (handler). - Кожна пастка відповідає одній внутрішній операції: `get`, `set`, `has`, `deleteProperty`, `ownKeys`, `apply`, `construct` та іншим. - Сигнатура пастки один в один збігається з однойменним методом `Reflect`. - Усередині пастки стандартну поведінку викликають через `Reflect`, а не через прямий доступ до `target`. - Якщо пастки для операції немає, операція виконується на `target` без змін. - Пастки `apply` і `construct` працюють лише тоді, коли `target`, це функція. ### Швидкий приклад ```javascript const user = { name: 'Maria' }; const proxy = new Proxy(user, { get(target, property) { console.log(`Reading property "${String(property)}"`); return target[property]; } }); console.log(proxy.name); // Reading property "name" // Maria ``` Тут пастка `get` перехоплює звернення до властивості, і перед поверненням значення можна зробити будь-що: логування, перевірку, валідацію, підміну значення. ### Синтаксис і обробник ```javascript const proxy = new Proxy(target, handler); ``` | Параметр | Опис | | --- | --- | | `target` | Вихідний об'єкт, справжня «ціль» | | `handler` | Об'єкт з пастками, тобто методами, що перехоплюють дії | ### Повний список основних пасток | Пастка | Що перехоплює | Аналог | | --- | --- | --- | | `get(target, prop, receiver)` | Читання властивості (`obj.prop`) | `obj[prop]` | | `set(target, prop, value, receiver)` | Запис властивості (`obj.prop = value`) | `obj[prop] = value` | | `has(target, prop)` | Перевірку `prop in obj` | `in` | | `deleteProperty(target, prop)` | Видалення (`delete obj[prop]`) | `delete` | | `ownKeys(target)` | Отримання списку ключів (`Object.keys`, `for...in`) | `Object.keys(obj)` | | `getOwnPropertyDescriptor(target, prop)` | Запит дескриптора властивості | `Object.getOwnPropertyDescriptor()` | | `defineProperty(target, prop, descriptor)` | Визначення нової властивості | `Object.defineProperty()` | | `getPrototypeOf(target)` | Отримання прототипа | `Object.getPrototypeOf()` | | `setPrototypeOf(target, proto)` | Зміну прототипа | `Object.setPrototypeOf()` | | `isExtensible(target)` | Перевірку розширюваності | `Object.isExtensible()` | | `preventExtensions(target)` | Заборону додавати нові властивості | `Object.preventExtensions()` | | `apply(target, thisArg, args)` | Виклик функції | `func()` | | `construct(target, args, newTarget)` | Виклик через `new` | `new func()` | ### Популярні пастки для об'єктів **`set`, контроль запису:** ```javascript const user = { name: 'Maria' }; const proxy = new Proxy(user, { set(target, prop, value) { if (prop === 'age' && typeof value !== 'number') { throw new TypeError('Age must be a number'); } target[prop] = value; return true; // обов'язково повернути true } }); proxy.age = 25; // ok proxy.age = 'twenty'; // TypeError ``` **`has`, підміна оператора `in`:** ```javascript const secret = { password: '1234' }; const proxy = new Proxy(secret, { has(target, key) { if (key === 'password') return false; return key in target; } }); console.log('password' in proxy); // false ``` Так можна «ховати» чутливі поля від перевірок наявності. **`ownKeys`, приховування властивостей при обході:** ```javascript const user = { name: 'Maria', password: '12345' }; const proxy = new Proxy(user, { ownKeys(target) { return Object.keys(target).filter(k => k !== 'password'); } }); console.log(Object.keys(proxy)); // ["name"] ``` ### Пастки для функцій: apply і construct **`apply`, перехоплення виклику функції:** ```javascript function sum(a, b) { return a + b; } const proxy = new Proxy(sum, { apply(target, thisArg, args) { console.log(`Called with arguments: ${args}`); return Reflect.apply(target, thisArg, args); } }); console.log(proxy(2, 3)); // Called with arguments: 2,3 // 5 ``` **`construct`, перехоплення виклику з `new`:** ```javascript function User(name) { this.name = name; } const proxy = new Proxy(User, { construct(target, args) { console.log('Creating a new user...'); return new target(...args); } }); const maria = new proxy('Maria'); // "Creating a new user..." ``` Обидві пастки спрацьовують лише тоді, коли `target`, це функція: загорнути звичайний об'єкт і перехопити `apply` не вийде. ### Як делегувати стандартну поведінку через Reflect Щоб не ламати нативну поведінку, усередині пасток зазвичай викликають `Reflect`: він виконує «справжню» операцію під капотом. ```javascript const proxy = new Proxy(user, { get(target, prop, receiver) { console.log(`Reading ${String(prop)}`); return Reflect.get(target, prop, receiver); } }); ``` Це гарантує, що все, чого ви не збиралися змінювати, лишиться таким, як за замовчуванням. Третій аргумент `receiver` тут не декоративний: він передає геттерам правильний `this`, а з простим `target[prop]` контекст губиться. ### Де реально застосовують пастки - Валідація і захист даних. - Логування доступу до властивостей. - Створення «віртуальних» властивостей, яких фізично немає в об'єкті. - Реактивні системи (Vue, MobX). - ORM та обгортки над API, де моделі підставляються динамічно. - Метапрограмування і тестові моки. | Термін | Що це | | --- | --- | | **Пастка (trap)** | Метод-обробник у `Proxy`, який перехоплює операцію з об'єктом | | **Handler** | Об'єкт, що містить пастки | | **Reflect** | Використовується всередині пасток для виклику стандартної поведінки | | **Target** | Справжній об'єкт, над яким стоїть `Proxy` | ### Типові помилки - **Не повертати булеве значення з `set`, `deleteProperty`, `has`, `defineProperty`.** Ці пастки зобов'язані повернути `true` або `false`; `undefined` трактується як `false`, і в strict mode операція впаде з `TypeError`. - **Використовувати `target[prop]` замість `Reflect.get(target, prop, receiver)`.** Так губиться `receiver`, і геттери та успадковані властивості отримують неправильний `this`. - **Забувати, що `prop` може бути `Symbol`.** Вставка символьного ключа в шаблонний рядок без `String(prop)` кине `TypeError`. - **Порушувати інваріанти.** Пастка не всесильна: `ownKeys` мусить повернути всі неконфігуровані власні ключі, а `get` не може віддати інше значення для неконфігурованої властивості з `writable: false`. Інакше рушій кине `TypeError`. - **Чекати на `apply` для об'єкта.** Пастки `apply` і `construct` працюють тільки з функціями. - **Вважати, що пастки перехоплюють усе.** Внутрішні слоти (наприклад, у `Map`, `Set`, `Date`) в обхід пасток звертаються до реального об'єкта, тому такі вбудовані типи через `Proxy` без прив'язки методів ламаються.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.