Перехватывай фундаментальные операции языка через ловушки Proxy и аккуратно делегируй поведение по умолчанию через Reflect.
Открыть этот урок в KodokonProxy оборачивает целевой объект и перехватывает внутренние операции языка через тринадцать ловушек: get, set, has (оператор in), deleteProperty, ownKeys, apply, construct и так далее. Объект Reflect предоставляет ровно те же операции в виде функций, с теми же сигнатурами, что и у ловушек. Это фундамент реактивности во Vue 3, динамических моков и API в стиле ORM.
const target = { name: "Ada" };
const traced = new Proxy(target, {
get(obj, prop, receiver) {
console.log("get " + String(prop));
return Reflect.get(obj, prop, receiver);
}
});
traced.name; // get nameПочему Reflect.get(obj, prop, receiver), а не obj[prop]? Из-за получателя (receiver). Если свойство - это аксессор (геттер), Reflect.get вызовет его с this, указывающим на получателя - на прокси или на объект, наследующий от него, - тогда как obj[prop] установил бы this на сырую цель. Без получателя геттеры, которые читают this, ломаются, как только какой-нибудь объект наследует от прокси.
const base = {
get label() {
return this.name;
}
};
const proxy = new Proxy(base, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
const child = Object.create(proxy);
child.name = "Ada";
console.log(child.label); // "Ada" thanks to the receiverСерьёзные сценарии применения: валидация при записи, виртуальные свойства, вычисляемые на лету, логирование, контроль доступа (скрытие ключей с префиксом _ через has и ownKeys), объекты с поведением по умолчанию. Учти, что Reflect.set возвращает булево значение вместо того, чтобы бросать исключение в строгом режиме: твоя ловушка обязана пробросить это возвращаемое значение.
function validated(target) {
return new Proxy(target, {
set(obj, prop, value, receiver) {
if (prop === "age" && !Number.isInteger(value)) {
throw new TypeError("age must be an integer");
}
return Reflect.set(obj, prop, value, receiver);
}
});
}
const user = validated({ age: 30 });
user.age = 31; // ok
// user.age = "old"; // TypeErrorget стоит предпочесть Reflect.get(target, prop, receiver), а не target[prop]?this (получателем), а не с сырой цельюtarget[prop] заново вызвал бы ловушку и привёл к бесконечной рекурсии"id" in obj?get возвращает 42 для свойства цели, значение которого равно 1 и которое объявлено неконфигурируемым и незаписываемым. Что произойдёт?