7 примеров использования Proxy

Источник: «7 Use Cases for Javascript Proxies»
Объект Proxy в JavaScript — удобный инструмент, открывающий мир возможностей и позволяющий создавать в приложениях полезное поведение. В сочетании с TypeScript Proxy расширяет возможности управления и манипулирования объектами и функциями такими способами, о которых вы даже не подозревали. В статье рассмотрим невероятную пользу Proxy на практических примерах.

Что такое Proxy

Proxy в Javascript — это обёртка вокруг другого объекта (цели), позволяющая перехватывать и переопределять фундаментальные операции для этого объекта, такие как поиск свойств, присваивание, перечисление и вызов функций. Это означает, что можно добавить пользовательскую логику при получении или установке свойств. Это отлично подходит для обработки валидации, уведомлений или даже автоматического связывания данных.

Создание простого Proxy

Давайте посмотрим, как создать Proxy. Начнём с самого простого примера, если вы раньше не сталкивались с Proxy.

type MessageObject = {
message: string;
};

let target: MessageObject = {
message: "Hello, world!"
};

let handler: ProxyHandler<MessageObject> = {
get: (obj, prop) => {
return `Property ${String(prop)} is: ${obj[prop]}`;
}
};

let proxy: MessageObject = new Proxy(target, handler);
console.log(proxy.message); // Output: Property message is: Hello, world!

В этом примере каждый раз, когда происходит обращение к свойству прокси, вызывается метод get обработчика, что позволяет изменить поведение простого обращения к свойству. Уверен, вы можете представить себе все открывающиеся возможности.

А теперь давайте рассмотрим ещё 7 полезнейших примеров!

1. Автоматическое заполнение свойств

Proxy могут динамически заполнять свойства объекта при обращении к нему, что удобно для обработки по требованию или инициализации сложных объектов.

type LazyProfile = {
firstName: string;
lastName: string;
fullName?: string;
};

let lazyProfileHandler = {
get: (target: LazyProfile, property: keyof LazyProfile) => {
if (property === "fullName" && !target[property]) {
target[property] = `${target.firstName} ${target.lastName}`;
}
return target[property];
}
};

let profile: LazyProfile = new Proxy({ firstName: "John", lastName: "Doe" }, lazyProfileHandler);
console.log(profile.fullName); // Output: John Doe

2. Подсчёт операций

Используйте Proxy для подсчёта сколько раз определённые операции выполняются над объектом. Это удобно при отладке, мониторинге или профилировании производительности приложения.

type Counter = {
[key: string]: any;
_getCount: number;
};

let countHandler = {
get: (target: Counter, property: keyof Counter) => {
if (property === "_getCount") {
return target[property];
}
target._getCount++;
return target[property];
}
};

let counter: Counter = new Proxy({ a: 1, b: 2, _getCount: 0 }, countHandler);
counter.a;
counter.b;
console.log(counter._getCount); // Output: 2

3. Иммутабельные объекты

Создавайте действительно неизменяемые объекты с помощью Proxy, перехватывая и предотвращая любые изменения объекта после его создания.

function createImmutable<T extends object>(obj: T): T {
return new Proxy(obj, {
set: () => {
throw new Error("This object is immutable");
}
});
}

const immutableObject = createImmutable({ name: "Jane", age: 25 });
// immutableObject.age = 26; // Throws error

4. Цепочка методов и гибкие интерфейсы

Усовершенствуйте цепочку методов, используя Proxy для создания гибких интерфейсов, где каждый вызов метода возвращает Proxy для дальнейших вызовов.

type FluentPerson = {
setName(name: string): FluentPerson;
setAge(age: number): FluentPerson;
save(): void;
};

function FluentPerson(): FluentPerson {
let person: any = {};

return new Proxy({}, {
get: (target, property) => {
if (property === "save") {
return () => { console.log(person); };
}
return (value: any) => {
person[property] = value;
return target;
};
}
}) as FluentPerson;
}

const person = FluentPerson();
person.setName("Alice").setAge(30).save(); // Output: { setName: 'Alice', setAge: 30 }

5. Умное кэширование

Это один из моих любимых вариантов использования. Применение механизмов умного кэширования, когда данные извлекаются или вычисляются по требованию, а затем сохраняются для последующего быстрого доступа.

function smartCache<T extends object>(obj: T, fetcher: (key: keyof T) => any): T {
const cache: Partial<T> = {};
return new Proxy(obj, {
get: (target, property: keyof T) => {
if (!cache[property]) {
cache[property] = fetcher(property);
}
return cache[property];
}
});
}

const userData = smartCache({ userId: 1 }, (prop) => {
console.log(`Fetching data for ${String(prop)}`);
return { name: "Bob" }; // Simulated fetch
});

console.log(userData.userId); // Output: Fetching data for userId, then returns { name: "Bob" }

6. Динамическая валидация свойств

Proxy могут динамически применять правила для назначения свойств. Так можно обеспечить выполнение определённых условий перед изменением свойств:

let user = {
age: 25
};

let validator = {
set: (obj, prop, value) => {
if (prop === 'age' && (typeof value !== 'number' || value < 18)) {
throw new Error("User must be at least 18 years old.");
}
obj[prop] = value;
return true; // Indicate success
}
};

let userProxy = new Proxy(user, validator);
userProxy.age = 30; // Works fine
console.log(userProxy.age); // Output: 30
// userProxy.age = 'thirty'; // Throws error
// userProxy.age = 17; // Throws error

7. Отслеживание изменений

Частым случаем использования Proxy является создание отслеживаемых объектов, уведомляющих о происходящих изменениях.

function onChange(obj, onChange) {
const handler = {
set: (target, property, value, receiver) => {
onChange(`Property ${String(property)} changed to ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
return new Proxy(obj, handler);
}

const person = { name: "John", age: 30 };
const watchedPerson = onChange(person, console.log);

watchedPerson.age = 31; // Console: Property age changed to 31

Недостатки использования Proxy

Хотя Proxy очень удобны, у них есть несколько недостатков:

  1. Производительность: Прокси могут снижать производительность, особенно при высокочастотных операциях, поскольку каждая операция с прокси должна проходить через обработчик.
  2. Сложность: С большой мощью приходит и большая сложность. Неправильное использование Proxy может привести к трудно отлавливаемым проблемам и проблемам с поддержкой.
  3. Совместимость: Proxy не могут быть реализованы для старых браузеров, не поддерживающих функции ES6, что ограничивает их использование в средах, требующих широкой совместимости.

Заключение

Proxy в JavaScript, особенно при использовании TypeScript, предоставляют гибкий способ взаимодействия с объектами. Они позволяют выполнять такие действия, как валидация, наблюдение и привязка. Создаёте ли вы сложные пользовательские интерфейсы, разрабатываете игры или работаете над логикой на стороне сервера, понимание и использование Proxy может обеспечить более высокий уровень контроля и изощрённости в коде. Спасибо за чтение и надеюсь, что вы узнали что-то новое!

Дополнительные материалы

Предыдущая Статья

Валидация JSON с JSON Schema и PHP

Следующая Статья

Полиморфные модели Laravel по типам