Proxy и Reflect

27-10-24 12:03:06


Image for the Proxy и Reflect

Что такое Прокси (Proxy)?

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

Конструктор Proxy принимает два аргумента:

  • target: Оригинальный объект, который вы хотите обернуть прокси.
  • handler: Объект, содержащий методы (называемые ловушками), которые определяют пользовательское поведение для операций над объектом.

Вот простой пример, иллюстрирующий использование прокси:

// Целевой объект
const target = {
message: "Привет, мир!"
};

// Обработчик с пользовательской ловушкой 'get'
const handler = {
get: function (obj, prop) {
if (prop in obj) {
return obj[prop];
} else {
return `Свойство "${prop}" не существует.`;
}
}
};

// Создание прокси
const proxy = new Proxy(target, handler);

console.log(proxy.message); // Вывод: Привет, мир!
console.log(proxy.nonExistentProp); // Вывод: Свойство "nonExistentProp" не существует.

В этом примере:

  • Объект target имеет свойство message.
  • Объект handler определяет ловушку get, которая настраивает поведение при доступе к свойствам. Если свойство существует в целевом объекте, оно возвращает его значение; в противном случае возвращает сообщение.
  • Объект proxy оборачивает target и перехватывает все операции get.

Основные ловушки (traps) в прокси

Вот несколько распространенных ловушек, которые можно использовать в обработчике прокси:

  1. get: Перехватывает чтение свойства.
  2. set: Перехватывает запись свойства.
  3. has: Перехватывает оператор in.
  4. deleteProperty: Перехватывает операции delete.
  5. apply: Перехватывает вызовы функций.
  6. construct: Перехватывает оператор new.
  7. ownKeys: Перехватывает Object.keys(), Object.getOwnPropertyNames() и другие операции перечисления свойств.

Давайте рассмотрим пример, в котором мы перехватываем как чтение, так и запись свойства.

const user = {
name: "Джон"
};

const handler = {
get(target, prop) {
console.log(`Доступ к свойству: ${prop}`);
return prop in target ? target[prop] : "Свойство не существует";
},
set(target, prop, value) {
if (typeof value === 'string') {
console.log(`Установка свойства: ${prop} на значение ${value}`);
target[prop] = value;
return true;
} else {
console.log(`Не удалось установить свойство: ${prop}. Значение должно быть строкой.`);
return false;
}
}
};

const proxyUser = new Proxy(user, handler);

console.log(proxyUser.name); // Вывод: Доступ к свойству: name \n Джон
proxyUser.age = 25; // Вывод: Не удалось установить свойство: age. Значение должно быть строкой.
proxyUser.age = "25"; // Вывод: Установка свойства: age на значение 25
console.log(proxyUser.age); // Вывод: Доступ к свойству: age \n 25

В этом примере:

  • Ловушка get выводит информацию о доступе к свойству и возвращает его значение, если оно существует; в противном случае — сообщение.
  • Ловушка set позволяет установить свойство, только если значение является строкой.

API Reflect

Объект Reflect предоставляет способ вызова встроенных операций JavaScript в виде функций. Каждая функция в объекте Reflect соответствует ловушке прокси и имеет то же имя и функциональность. Это позволяет легко использовать поведение по умолчанию при реализации ловушек в прокси.

​Пример использования Reflect в прокси

​const person = {
firstName: "Алиса",
lastName: "Смит"
};

const handler = {
get(target, prop) {
console.log(`Получение ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`Установка ${prop} на значение ${value}`);
return Reflect.set(target, prop, value);
}
};

const proxyPerson = new Proxy(person, handler);

console.log(proxyPerson.firstName); // Вывод: Получение firstName \n Алиса
proxyPerson.age = 30; // Вывод: Установка age на значение 30
console.log(proxyPerson.age); // Вывод: Получение age \n 30

В этом примере:

  • Методы Reflect.get и Reflect.set используются для выполнения стандартного поведения чтения и записи свойств.

Зачем использовать Reflect?

Использование Reflect гарантирует выполнение стандартного поведения, что может быть полезно для:

  • Упрощения кода путем делегирования операций на стандартное поведение.
  • Поддержания согласованности при добавлении пользовательского поведения в прокси.

Валидация значений свойств с помощью Proxy и Reflect

Можно использовать прокси для проверки значений перед их установкой.

const settings = {
brightness: 50
};

const handler = {
set(target, prop, value) {
if (prop === 'brightness' && (value < 0 || value > 100)) {
throw new Error("Яркость должна быть в пределах от 0 до 100.");
}
return Reflect.set(target, prop, value);
}
};

const proxySettings = new Proxy(settings, handler);

try {
proxySettings.brightness = 120; // Ошибка: Яркость должна быть в пределах от 0 до 100.
} catch (e) {
console.error(e.message);
}

proxySettings.brightness = 80; // Успех
console.log(proxySettings.brightness); // Вывод: 80

В этом примере:

  • Ловушка set проверяет, находится ли значение brightness в заданном диапазоне. Если нет, она выбрасывает ошибку.

Динамический импорт с прокси

Вы можете использовать динамические импорты вместе с прокси для условной загрузки модулей.

const handler = {
get(target, prop) {
if (prop === 'loadModule') {
return async (moduleName) => {
const module = await import(`./${moduleName}.js`);
return module;
};
}
return Reflect.get(target, prop);
}
};

const moduleLoader = new Proxy({}, handler);

// Динамическая загрузка модуля
moduleLoader.loadModule('utils')
.then(module => {
console.log(module);
})
.catch(err => {
console.error('Не удалось загрузить модуль:', err);
});

В этом примере:

  • Прокси перехватывает вызовы к loadModule, динамически импортируя запрашиваемый модуль с помощью функции import().