Skip to main content

Пастки (traps) у Proxy

Пастки (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 objin
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)Виклик через newnew 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 без прив'язки методів ламаються.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.