Понимание силы прокси в JavaScript
29 марта 2023 г.За прошедшие годы JavaScript превратился в мощный и адаптируемый язык программирования, постоянно развивающийся в соответствии с меняющимися потребностями разработчиков.
Одним из относительно недавних достижений является объект Proxy, который позволяет программистам создавать мощные и гибкие объекты, способные перехватывать и изменять ключевые операции над другими объектами.
В этой статье рассматриваются возможности прокси-сервера в JavaScript, его синтаксис, атрибуты, типичные приложения, сильные стороны, ограничения, иллюстративные примеры и рекомендуемые подходы.
Что такое прокси?
Прокси — это объект, который окружает другой объект и перехватывает основные операции с ним, такие как доступ, назначение и удаление свойств. Прокси-сервер — это важнейший аспект JavaScript, позволяющий разработчикам писать более универсальный и надежный код.
Цель этой статьи — предоставить всестороннее представление о прокси-сервере в JavaScript, включая его синтаксис, характеристики, преимущества и недостатки, иллюстрации и рекомендуемые методы.
Синтаксис и свойства прокси
Прокси-сервер JavaScript – это возможность, позволяющая создавать объекты, способные изменять и настраивать основные операции, выполняемые над другими объектами.
Создание прокси-объекта
Для создания прокси-объекта необходимы два компонента: целевой объект и объект-обработчик. Целевой объект — это тот, над которым должны перехватываться операции, а объект-обработчик отвечает за удержание ловушек или методов, используемых для перехвата этих операций.
Вот пример, демонстрирующий создание базового объекта Proxy:
const target = {
name: 'John',
age: 25,
};
const handler = {
get: function(target, prop) {
console.log(`Getting property ${prop}`);
return target[prop];
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name);
// Getting property name
// John
В этом примере мы создаем целевой объект, который имеет две характеристики: имя и возраст. Мы также создаем объект-обработчик с ловушкой get для захвата любых попыток чтения свойства целевого объекта. После этого мы создаем объект Proxy, предоставляя целевые объекты и объекты-обработчики конструктору Proxy. Наконец, мы извлекаем свойство name объекта Proxy, которое вызывает ловушку get и выводит сообщение на консоль.
Ловушки и их поведение
Ловушки — это методы, которые перехватывают операции над целевым объектом. Есть несколько ловушек, которые вы можете использовать с прокси-объектом, включая get, set, has, deleteProperty и другие.
Вот краткий обзор некоторых наиболее часто используемых ловушек:
- get: эта ловушка перехватывает попытки прочитать свойство целевого объекта. Он принимает два аргумента: целевой объект и свойство, к которому осуществляется доступ. Ловушка возвращает значение свойства.
2. set: эта ловушка фиксирует любые попытки установить свойство целевого объекта. Для этого требуются три параметра: сам целевой объект, устанавливаемое свойство и обновленное значение этого свойства. Механизм имеет возможность изменить устанавливаемое значение или может сгенерировать ошибку, запрещающую установление значения.
3. has: эта ловушка перехватывает попытки проверить, существует ли свойство в целевом объекте. Он принимает два аргумента: целевой объект и проверяемое свойство. Ловушка возвращает логическое значение, указывающее, существует ли свойство.
4. deleteProperty: эта ловушка перехватывает попытки удалить свойство из целевого объекта. Он принимает два аргумента: целевой объект и удаляемое свойство. Ловушка может удалить свойство или вызвать ошибку, чтобы предотвратить удаление свойства.
Отзывные прокси
Прокси-объекты обладают интересной характеристикой, которая позволяет сделать их недействительными, в результате чего их ловушки больше не перехватывают операции над целевым объектом. Чтобы создать объект Proxy, который может быть признан недействительным, используйте функцию Proxy.revocable()
.
Вот пример:
const target = {
name: 'John',
age: 25,
};
const handler = {
get: function(target, prop) {
console.log(`Getting property ${prop}`);
return target[prop];
},
};
const {proxy, revoke} = Proxy.revocable(target, handler);
console.log(proxy.name);
// Getting property name
// John
revoke();
console.log(proxy.name);
// Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
В этом примере мы создаем отзывный объект Proxy с помощью метода Proxy.revocable()
. Затем мы обращаемся к свойству name объекта Proxy, которое запускает ловушку get и записывает сообщение в консоль. Затем мы отзываем объект Proxy с помощью метода revoke()
, что означает, что любые дальнейшие попытки доступа к свойствам объекта Proxy потерпят неудачу.
Наследование через прокси
Еще одна интересная особенность объектов Proxy заключается в том, что их можно использовать для реализации шаблонов наследования в JavaScript. Используя объект Proxy в качестве прототипа другого объекта, вы можете перехватывать поиск свойств и настраивать поведение цепочки прототипов.
Вот пример:
const parent = {
name: 'John',
};
const handler = {
get: function(target, prop) {
console.log(`Getting property ${prop}`);
if (!(prop in target)) {
return Reflect.get(parent, prop);
}
return target[prop];
},
};
const child = new Proxy({}, handler);
console.log(child.name);
// Getting property name
// John
child.name = 'Bob';
console.log(child.name);
// Getting property name
// Bob
console.log(parent.name); // John
В этом примере родительский объект определен и имеет атрибут имени. Затем мы создаем объект-обработчик с ловушкой get, которая предотвращает любые запросы на чтение свойств дочернего объекта. Ловушка использует метод Reflect.get() для возврата к родительскому объекту, если свойство отсутствует в дочернем объекте.
Затем, используя объект Proxy в качестве прототипа и объект-обработчик в качестве обработчика, мы создаем дочерний объект. Наконец, мы получаем доступ к свойству name дочернего объекта и изменяем его, что запускает ловушки get и set и записывает сообщения в консоль.
Реальные примеры использования прокси
Кэширование
Одним из вариантов использования прокси-сервера является кэширование дорогостоящих вызовов функций. В этом примере мы создаем объект Proxy, который кэширует результат вызова функции на основе ее аргументов.
function calculateCost(price, taxRate) {
console.log('Calculating cost...');
return price * (1 + taxRate);
}
const cache = new Map();
const proxy = new Proxy(calculateCost, {
apply(target, thisArg, args) {
const key = args.join('-');
if (cache.has(key)) {
console.log('Returning cached result...');
return cache.get(key);
} else {
const result = Reflect.apply(target, thisArg, args);
cache.set(key, result);
return result;
}
},
});
console.log(proxy(10, 0.2)); // Calculating cost... 12
console.log(proxy(10, 0.2)); // Returning cached result... 12
console.log(proxy(20, 0.2)); // Calculating cost... 24
console.log(proxy(20, 0.3)); // Calculating cost... 26
console.log(proxy(20, 0.3)); // Returning cached result... 26
В этом примере мы определяем функцию с именем calculateCost
, которая принимает цену и ставку налога и возвращает стоимость с налогом. Затем мы создаем объект кеша, используя класс Map
.
Затем мы создаем объект Proxy с именем proxy
, который перехватывает вызовы функций с помощью ловушки apply
. Ловушка apply
вызывается всякий раз, когда вызывается функция, и получает аргументы функции в виде массива. Мы используем аргументы для генерации ключа кеша и проверяем, есть ли уже результат в кеше. Если это так, мы возвращаем кешированный результат. В противном случае мы вычисляем результат и сохраняем его в кеше.
Наконец, мы вызываем функцию proxy
с разными аргументами и наблюдаем, что результат сохраняется в кеше для последующих вызовов с идентичными аргументами.
Проверка
Еще один вариант использования прокси-сервера — проверка свойств объекта. В этом примере мы создаем объект Proxy, который проверяет длину строкового свойства.
const user = {
name: 'John',
password: 'secret',
};
const proxy = new Proxy(user, {
set(target, prop, value) {
if (prop === 'password' && value.length < 8) {
throw new Error('Password must be at least 8 characters long');
}
target[prop] = value;
return true;
},
});
console.log(proxy.name); // John
console.log(proxy.password); // secret
proxy.password = '12345678';
console.log(proxy.password); // 12345678
proxy.password = '123'; // Error
В этом примере мы определяем объект с именем user
с name
и свойством password
. Затем мы создаем объект Proxy с именем proxy
, который перехватывает присвоения свойств с помощью ловушки set
. Ловушка set
вызывается всякий раз, когда присваивается свойство, и получает имя свойства, новое значение и целевой объект.
Мы используем ловушку set
, чтобы проверить, является ли назначаемое свойство свойством password
и имеет ли значение длину менее 8 символов. Если это так, мы выдаем ошибку. В противном случае мы устанавливаем значение свойства для целевого объекта.
Мы используем объект proxy
для присвоения различных значений свойству password
и замечаем, что любые значения длиной менее 8 символов вызывают ошибку.
Ведение журнала
Другим распространенным вариантом использования прокси-сервера является регистрация доступа к свойствам объекта и их назначения. В этом примере мы создаем объект Proxy, который регистрирует доступ к свойствам и их назначения.
const user = {
name: 'John',
email: 'john@example.com',
};
const proxy = new Proxy(user, {
get(target, prop) {
console.log(`Getting ${prop} property`);
return target[prop];
},
set(target, prop, value) {
console.log(`Setting ${prop} property to ${value}`);
target[prop] = value;
return true;
},
});
console.log(proxy.name); // Getting name property -> John
proxy.email = 'bob@example.com'; // Setting email property to bob@example.com
console.log(proxy.email); // Getting email property -> bob@example.com
В этом примере мы определяем объект с именем user
с name
и свойством email
. Затем мы создаем объект Proxy с именем proxy
, который перехватывает доступ к свойствам и назначения с помощью ловушек get
и set
.
Ловушка get
вызывается при каждом доступе к свойству и получает имя свойства и целевой объект. В этом примере мы записываем в консоль сообщение о том, что к свойству осуществляется доступ, а затем возвращаем значение свойства из целевого объекта.
Ловушка set
вызывается всякий раз, когда присваивается свойство, и получает имя свойства, новое значение и целевой объект. В этом примере мы записываем в консоль сообщение о том, что свойство назначается, а затем устанавливаем значение свойства для целевого объекта.
Наконец, мы получаем доступ к различным свойствам и назначаем их с помощью объекта proxy
и наблюдаем, что сообщения регистрируются на консоли.
Преимущества и ограничения прокси-сервера
Преимущества прокси-сервера
- Настраиваемое поведение. С помощью прокси-объектов вы можете перехватывать и настраивать базовые операции над другими объектами, что позволяет создавать расширенные функции, такие как контроль доступа, кэширование и ведение журнала.
2. Наследование. Прокси-объекты позволяют реализовать шаблоны наследования в JavaScript, что позволяет создавать более универсальный и масштабируемый код.
3. Отзываемый: прокси-объекты могут быть отключены или отозваны после их создания, что делает их полезными для ограничения области действия прокси-объекта или по соображениям безопасности.
Ограничения прокси-сервера
- Несмотря на то, что прокси уже давно с нами, не все версии браузеров могут поддерживать эту функциональность.
2. Более того, использование прокси может негативно сказаться на производительности вашего приложения, особенно если вы используете их слишком часто.
3. Важно понимать смысл использования прокси. Ему не следует доверять в критически важных для приложения моментах, таких как важная проверка пользовательского ввода.
Рекомендации по использованию прокси-сервера
Учитывайте ограничения. Прежде чем внедрять прокси-сервер в свой код, узнайте об ограничениях, которые он накладывает, и о том, как они могут повлиять на скорость и безопасность вашего приложения.
Прокси-объекты следует использовать только в случае крайней необходимости, поскольку они могут повлиять на производительность вашего кода.
Тщательно тестируйте: при использовании прокси-объектов обязательно тщательно тестируйте и будьте готовы к любому потенциально неожиданному поведению.
Соблюдайте нормы. Чтобы сделать ваш код простым для чтения и обслуживания, придерживайтесь общепринятых соглашений и рекомендаций при реализации прокси-объектов.
Заключение
В статье рассматриваются расширенные функции Proxy, такие как шаблоны наследования и возможность создания отзываемых объектов Proxy.
Независимо от вашего уровня опыта в качестве разработчика, понимание прокси в JavaScript имеет основополагающее значение для повышения вашего кода до более высокого уровня.
Благодаря своей адаптируемости и эффективности Proxy представляет собой важнейший инструмент для любого разработчика JavaScript, стремящегося с легкостью создавать сложные приложения.
Оригинал