Что такое прокси?
Прокси, в широком смысле, это некая доверенная сущность, выступающая от имени другой сущности. Прокси - это заменитель реального объекта, у которого есть право выступать от имени и в интересах этого объекта. "Объектом" в данном случае может быть практически всё что угодно. Если же ограничиться рассмотрением прокси в применении к JavaScript, то можно сказать, что это - особые объекты, которые позволяют перехватывать и изменять действия, выполняемые над другими объектами. В частности, речь идёт о вызове функций, об операциях присваивания, о работе со свойствами, о создании новых объектов, и так далее. Эту технологию используют для блокирования прямого доступа к целевому объекту или целевой функции и организации взаимодействия с объектом или функцией через прокси-объект.
Прежде чем мы продолжим разговор о прокси-объектах и перейдём к примерам работы с ними, рассмотрим три важных термина, имеющих к ним непосредственное отношение.
Синтаксис
Вот как выглядит объявление простого прокси-объекта, которому передаётся целевой объект и обработчик.
let proxy = new Proxy(target, handler)
Проверка поддержки прокси-объектов браузером
Начнём с проверки поддержки прокси-объектов JavaScript браузером.
let proxyTest = new Proxy({}, {}) if (proxyTest instanceof Object) { document.write("Proxy supported!") }
Стандартное поведение объектов
Здесь мы объявим объект, а затем попробуем обратиться к несуществующему свойству этого объекта.
let obj = { c: "car", b: "bike" } document.write(obj.b) //Результат -> "bike" document.write(obj.c) //Результат -> "car" document.write(obj.l) //Результат -> "undefined"
Использование прокси для объекта
В следующем примере мы используем обработчик с перехватчиком get. Обработчик передаст целевой объект и запрошенный ключ перехватчику.
let handler = { get: function(target, name) { return name in target ? target[name] : "Key does not exist" } } let obj = { c: "car", b: "bike" } let proxyObj = new Proxy(obj, handler) document.write(proxyObj.b) //Результат -> "bike" document.write(proxyObj.c) //Результат -> "car" document.write(proxyObj.l) //Результат -> "Key does not exist"
Перехватчик get
Перехватчик get выполняется при попытке доступа к свойству объекта с использованием прокси. Метод get принимает параметр target (объект, с которым нужно работать через прокси) и property (свойство, к которому мы пытаемся получить доступ).
Синтаксис
var p = new Proxy(target, { get: function(target, property, receiver) {} })
Пример
В следующем примере мы попытаемся воздействовать на значение с помощью перехватчика get до вывода его на экран.
let handler = { get: function(target, name) { return name in target ? target[name]*10 : "Key does not exist" } } let obj = { a: 1, b: 2 } let proxyObj = new Proxy(obj, handler) document.write(proxyObj.a) //Результат -> 10 document.write(proxyObj.b) //Результат -> 20 document.write(proxyObj.c) //Результат -> "Key does not exist"
Перехватчик set
Перехватчик set выполняется при попытке установки свойства объекта с использованием прокси. Метод set принимает параметр target (объект, доступ к которому мы собираемся получить), property (свойство, которое мы собираемся устанавливать), и value (значение свойства, которое мы собираемся установить).
Синтаксис
var p = new Proxy(target, { set: function(target, property, value, receiver) {} })
Пример
В следующем примере мы добавим к объекту некоторые свойства, назначим им значения, при этом сделаем это до объявления прокси-объекта (в коде он носит имя proxyObj).
Проанализировав этот пример, можно заметить, что если после объявления proxyObj попытаться задать новое свойство объекта, будет вызван перехватчик и в объекте будет сохранено значение свойства, изменённое им.
let handler = { set: function(target, name, value) { target[name] = value*10 } } let obj = { a: 1, b: 2 } let proxyObj = new Proxy(obj, handler) proxyObj.c = 3 document.write(proxyObj.a) //Результат -> 1 document.write(proxyObj.b) //Результат -> 2 document.write(proxyObj.c) //Результат -> 30
Перехватчик has
Перехватчик has вызывается при выполнении оператора in. Метод has принимает параметры target (целевой объект) и property (свойство, доступ к которому нужно контролировать с помощью прокси-объекта).
Синтаксис
var p = new Proxy(target, { has: function(target, property) {} })
Пример
В следующем примере мы проверяем, входит ли в ключ подстрока ar. Для начала проверим, существует ли ключ, и, если это так, проверим, содержит ли он интересующую нас подстроку. Если будут выполнены оба условия, мы вернём логическое значение true, в противном случае - вернём false.
const handler = { has: function(target, key) { if (key in target && key.includes("ar")) { return true } return false } } const user = { car: 'Bentley', bar: 4, hotel: "no", } const proxy = new Proxy(user, handler) document.write('car' in proxy) // Результат -> true document.write('car' in user) // Результат -> true document.write('hotel' in proxy) // Результат -> false document.write('hotel' in user) // Результат -> true document.write('spacebar' in proxy) // Результат -> false document.write('spacebar' in user) // Результат -> false
Перехватчик apply
Перехватчик apply позволяет вызывать прокси с параметрами. Он позволяет переопределять функции. Метод apply принимает параметры target (целевой объект или целевая функция), thisArg (аргумент this для использования при вызове) и argumentsList (список всех аргументов в виде массива).
Синтаксис
var p = new Proxy(target, { apply: function(target, thisArg, argumentsList) {} })
Пример
В следующем примере мы, с помощью прокси, переопределим функцию, которая умножает два числа. Прокси меняет поведение функции, прибавляя 1 к результату умножения.
function multiply(a, b) { return a * b } const handler = { apply: function(target, thisArg, argumentsList) { return target(argumentsList[0], argumentsList[1]) + 1 } } var proxy = new Proxy(multiply, handler) document.write(multiply(2, 5)) // Результат -> 10 document.write(proxy(2, 5)) // Результат -> 11
Перехватчик construct
Перехватчик construct выполняется при вызове оператора new. Для того чтобы этот перехватчик мог нормально функционировать, нужно, чтобы целевой объект, для работы с которым его планируется вызывать, можно было бы создать командой вида new Target().
Синтаксис
var p = new Proxy(target, { construct: function(target, argumentsList, newTarget) {} })
Пример
В следующем примере мы передадим через прокси новое значение, к которому добавлен символ валюты, функции-конструктору.
function formatCurrency(format) { this.format = format } const handler = { construct: function(target, args) { return new target("$" + args[0]) } } const proxy = new Proxy(formatCurrency, handler) document.write(new proxy('100').format) // Результат -> $100
Перехватчик deleteProperty
Перехватчик deleteProperty вызывается при обращении к методу delete. Он принимает параметры target (целевой объект или целевая функция), и property (свойство, команду удаления которого мы хотим обрабатывать).
Синтаксис
var p = new Proxy(target, { deleteProperty: function(target, property) {} })
Пример
Следующий пример демонстрирует вызов нужной нам функции и выполнение определённых действий при попытке удаления свойства объекта.
const cars = { merc: 's320', buggati: 'veyron', } const handler = { deleteProperty: function(target, prop) { if (prop in target) { document.write(`${prop} has been removed`) delete target[prop] } } } document.write(cars.merc) // Результат -> "s320" const proxy = new Proxy(cars, handler) delete proxy.merc // Результат -> merc has been removed document.write(cars.merc) // Результат -> undefined
Варианты использования прокси
Вот несколько областей практического применения прокси-объектов в JavaScript.