Proxyhandler что это
Прокси (Proxy) представляет объект, который позволяет перехватывать выполнение операций по отношению к некоторому другому объекту и может переопределять его поведение.
Для создания объекта Proxy применяется конструктор Proxy() :
const proxy = new Proxy(target, handler);
Конструктор Proxy принимает два параметра:
- target — цель создания прокси, это может быть любой объект, к которому применяется Proxy
- handler — другой объект, который определяет, какие именно операции объекта target будут перехватываться и переопределяться и как именно.
Рассмотрим простейший пример:
// объект, к которому применяется прокси const target = ; // объект, который определяет, как будет переопределяться target const handler = <>; // объект прокси const proxy = new Proxy(target, handler); console.log(proxy.name); // Tom
Итак, в примере выше target — это объект, к которому будет применяться проксирование. В данном случае этот объект имеет свойство name .
const target = ;
Далее создается пустой обработчик handler:
const handler = <>;
В принципе этот объект должен определять, как будет переопределяться объект target. Но пока оставим его пустым.
Затем создаем объект Proxy, передавая в его конструктор объекты target и handler .
const proxy = new Proxy(target, handler);
Проксирование объекта (в данном случае объекта target ) означает, что через прокси мы можем обращаться к функциональности этого объекта. И в данном случае через объект proxy мы можем обратиться к свойству name проксированного объекта target :
console.log(proxy.name); // Tom
И поскольку мы использовали пустой handler, который ничего не переопределяет, то по сути прокси ведет себя как оригинальный объект target.
Переопределение функциональности объекта
Выше мы выполнили проксирование объекта, но пока никак не переопределяли его поведение. Ключевым в данном случае является определение обработчика handler , который может перехватывать обращения к свойствам проксированного объекта. Этот обработчик может определять два метода: get и set .
Метод get и получение свойств объекта
Метод get перехватывает обращения к свойству при получении его значения и возвращает для этого свойства некоторое значение:
const handler = < get: function(target, prop, receiver) < return некоторое_значение; >>;
Метод get имеет три параметра:
- target : сам проксированный объект. Благодаря этому параметру мы можем обратиться к функциональности оригинального объекта
- prop : название свойства, к которому идет обращение
- receiver : объект Proxy, через который выполняется проксирование
Возьмем следующий пример:
const target = ; const handler = < get: function(target, prop, receiver) < return "Tomas Smith"; >>; const proxy = new Proxy(target, handler); console.log(proxy.name); // Tomas Smith
Здесь в обработчике handler в методе get возвращается строка «Tomas Smith»:
get: function(target, prop, receiver)
Это приведет к тому, что при обращение к любому свойству прокси-объекта будет возвращаться данная строка:
console.log(proxy.name); // Tomas Smith
Так, мы выполнили перехват обращения к свойству и простейшее переопределение. При этом мы можем обащаться и к оригинальному объекту, который проксируется:
const target = ; const handler = < get: function(target, prop) < return "Name: " + target.name; >>; const proxy = new Proxy(target, handler); console.log(proxy.name); // Name: Tom
Здесь обработчик возвращает строку «Name: » + target.name , где target.name представляет обращение к свойству name оригинального объекта. Естественно логика возвращение значения свойства может более сложной.
Но возьмем более сложный объект — с двумя свойствами:
const target = ; const handler = < get: function(target, prop) < return target[prop]; >>; const proxy = new Proxy(target, handler); console.log(proxy.name); // Tom console.log(proxy.age); // 37
Здесь целевой объект имеет два свойства: name и age. В обработчике мы перехватываем обращение к ним, но никак его не переопределяем, а просто возвращаем значения свойств оригинального объекта:
return target[prop];
Для обращения к свойствам целевого объекта применяется синтаксис массивов.
Но также мы можем проверить, к какому именно свойству идет обращение, и выполнить некоторое переопределение:
const target = ; const handler = < get: function(target, prop) < if(prop==="name") return target.name.toUpperCase(); else return target[prop]; >>; const proxy = new Proxy(target, handler); console.log(proxy.name); // TOM console.log(proxy.age); // 37
В данном случае, если обращение идет к свойству name , то есть к свойству, которое хранит строку, то вызываем у этой строки метод toUpperCase() и переводим ее в верхний регистр.
Установка свойства и метод set
Метод set перехватывает обращения к свойству при установке его значения:
const handler = < set: function(target, property, value, receiver) < >>;
Метод set имеет четыре параметра:
- target : оригинальный объект, к которому идет проксирование
- property : название свойства, к которому идет обращение
- value : устанавливаемое значение
- receiver : объект Proxy, через который выполняется проксирование
Рассмотрим на примере:
const target = ; const handler = < set: function(target, prop, value) < console.log(value); target[prop] = value; >>; const proxy = new Proxy(target, handler); proxy.name = "Tomas"; console.log(proxy.name); // Tomas proxy.age = 22; console.log(proxy.age); // 22
В данном примере в методе set сначала логирруем передаваеемое свойству значение, затем устанавливаем свойство:
target[prop] = value;
Немного изменим пример:
const target = ; const handler = < set: function(target, prop, value) < if(prop==="age" && value < 1) console.log("Некорректный возраст"); else return target[prop] = value; >>; const proxy = new Proxy(target, handler); proxy.name = "Tomas"; console.log(proxy.name); // Tomas proxy.age = -199; // Некорректный возраст console.log(proxy.age); // 37 proxy.age = 22; console.log(proxy.age); // 22
Здесь в методе set обработчика проверяем, если идет установка свойства age и значение меньше 1, то просто выводим сообщение о некорректности данных
if(prop==="age" && value < 1) console.log("Некорректный возраст");
Иначе передаем значение свойству оригинального объекта:
else return target[prop] = value;
Что такое PROXY. Установка и настройка. Инструкция
Proxy (прокси) в переводе с английского – “представитель, уполномоченный, посредник”.
Proxy-сервер – это сервер-посредник.
Как работает технология?
Сравним трафик с посылкой. Пользователь отправляет посылку по какому-то адресу, но не самостоятельно, а через третье лицо, чтобы не раскрывать свою геопозицию и имя. Удобно и просто.
Но помимо обеспечения конфиденциальности в сети, Proxy-серверы имеют еще и много других функций.
Минусы технологии:
- Proxy-серверы узконаправлены, и каждый тип интернет-соединения требует свой тип сервера.
- Основная проблема большинства Proxy – безопасность, ведь не все Proxy-серверы обеспечивают дополнительную шифровку трафика.
Использование такого Proxy-сервер сравнимо с передачей данных третьему лицу без шифра. Такое возможно, если доверяете агенту, как себе.
Важно! Не используйте бесплатные Proxy-серверы с сомнительной репутацией.
2. Отличия Proxy от VPN
- Технология VPN использует шифрование и передает именно зашифрованную информацию по туннелю, а Proxy – нет.
- 100% сетевого трафика, проходящего через VPN, защищено, через Proxy – лишь интернет-трафик.
- Запросы Proxy отправляет, как посредник, VPN туннелирует весь объем сетевой активности вплоть до операционной системы.
Для допуска сотрудников к корпоративным базам данных организации могут использовать VPN, чтобы трафик не перехватывали. В этом случае провайдер увидит не полноценную историю использования, а только то, что пользователь подключался к VPN.
3. Виды Proxy
- Прозрачный Proxy – самый примитивный вид. В браузере не прописываются настройки. Такой Proxy просто ловит идущий HTTP-трафик. Пользователь не ощущает, что работает через Proxy-сервер.
В качестве примера можно рассмотреть отслеживание развлекательных сайтов в сети образовательных организаций.
- Анонимный Proxy не отправляет IP-адрес пользователя на целевой ресурс. Подходящий вариант для пользователя, который заботится, чтобы таргетированная реклама отслеживала его действия и местоположение.
- Proxy высокой анонимности не направляет IP-адрес и персональные данные пользователя, не определяет себя в качестве Proxy. Периодическое обновление IP-адреса позволяет сохранить приватность. Такой тип Proxy-сервера применяется в браузере TOR. Инициатора запроса сложно отследить из-за постоянного обновления IP-адреса.
- Искажающий (distorting) Proxy работает подобно анонимному, с единственным исключением – отправляет заведомо ложный IP-адрес. Используется для обхода отдельных ограничений доступа к контенту.
- Резидентный (residential) Proxy работает через реальные (белые, статические) IP-адреса. Для серверов это выглядит, как запросы от обычных клиентов. Альтернатива резидентному – data center proxy, у которого IP-адреса не привязаны к устройству. Облачные провайдеры обладают высокоскоростным интернет-соединением. Но если один сервер размещает несколько сотен Proxy-серверов, то им будут присвоены идентичные IP-адреса.
- Публичный (public) Proxy – достаточно небезопасный вид. Может отпасть в любой момент, чувствителен к хакерским атакам. Списки бесплатных публичных Proxy найти легко, но найти приемлемый публичный Proxy практически невозможно.
- Частный (private) Proxy используется одномоментно только одним пользователем, но перед использованием происходит проверка достоверности данных. Такая версия публичного Proxy более надежна и может иметь высокую анонимность или прозрачность, как, например, резидентный или data center Proxy.
- Общий (shared) Proxy – один из наиболее дешевых видов Proxy-серверов. Арендная плата за сервер распределяется между пользователями, которым предоставляется одновременный доступ к нему.
- Ротационный (rotating) Proxy для каждого нового пользователя выделяет новый IP. Значит один и тот же IP-адрес не применяется больше одного раза. Ротационный сервер обладает высоким уровнем приватности и безопасности.
- SSL-Proxy работают, как HTTPS-запросы – запросы от пользователя к серверу защищены шифрованием.
- Обратный (reverse) Proxy демонстрирует иной подход к проксированию. Здесь скрывается не IP пользователя, а IP-адрес сервера, куда направляется запрос. Инструмент предназначен для контроля доступа к серверу и ограничения неконтролируемого доступа к базе данных, а также для снижения трафика благодаря кэшированию информации.
4. Где искать Proxy
Многие сервисы Proxy предлагают комбинации различных видов Proxy. Бывают случаи, когда резидентные, SSL-Proxy и высоко анонимные соединены в один сервис.Сервисы, на которых можно выбрать подходящий вариант:
- https://smartproxy.com/
- https://www.megaproxy.com/
- https://whoer.net/webproxy
- https://www.proxysite.com/
- https://hide.me/en/proxy
- https://www.kproxy.com/
- https://www.vpnbook.com/webproxy
5. Что делать, если у вас в стране заблокируют интернет внутри страны
Представим ситуацию, когда ряд иностранных ресурсов попадает под блокировку, доступ к ним вы не можете получить или получаете чрезвычайно медленно. У вас есть несколько вариантов решения этой проблемы:
- Создаем свое VPN-подключение через арендованный в другой стране виртуальный сервер.
- Настраиваем VPN-подключение через платный коммерческий VPN.
- Используем Proxy-сервер.
Любой из этих способов восстановит подключение к ресурсу, в случае если доступ перекрывается провайдером.
6. Что делать, если интернет заблокируют извне
Такая ситуация практически невозможна.
3 июня 2011 года была принята резолюция ООН, признающая доступ в интернет базовым правом человека. Таким образом, отключение тех или иных регионов от интернета является нарушением прав человека.
В наших соцсетях много полезной информации и ламповая атмосфера, присоединяйтесь! Сайт TTM Academy Telegram Дзен Medium Вконтакте YouTube
ProxyHandler что это за программа на Андроид?
Доброго времени уважаемые Хотел вам рассказать о программе ProxyHandler, но увы, ничего вообще в интернете не могу найти, никакой информации нет, уже просмотрел несколько сайтов и форумов и все попусту. Пришлось спускаться на дно глубинного интернета, там все таки я кое что узнал… Значит ProxyHandler (com.android.proxyhandler) это приложение, которое обеспечивает работу программ через прокси-сервер. Удалять не рекомендуется, ибо могут быть проблемы
Основной фал программы это ProxyHandler.apk, ну это так, на заметку вам. Но что такое это прокси-сервер, это вообще ЧТО? Попробую простыми словами обьяснить, смотрите, вот есть такие программы, которые работают с интернетом, верно? Ну например браузер Хром. И если вы этот браузер запускаете и пользуетесь им, то он постоянно держит соединения с интернетом. То есть это сетевая программа. Сайты могут понимать из какой вы страны по вашему IP-адресу (это нормально, никто за вами специально не следит). Но есть такая настройка как прокси, вы туда можете установить ДРУГОЙ IP-адрес и в итоге сайты будут видеть именно ЭТОТ IP-адрес, понимаете? Вот что такое прокси. Таким способом вы можете обмануть какой-то сайт и сделать так, будто вы из Америки, а не из России. Для этого нужно просто прописать американский IP-адрес в настрой прокси
Вот щас на одном авторитетном сайте, а именно на 4PDA я узнал, что ProxyHandler это СИСТЕМНОЕ ПРИЛОЖЕНИЕ РЕБЯТА. То есть удалять его даже не думайте, ибо это СИСТЕМА
Еще на одном сайте англоязычном, я узнал что таки да, ProxyHandler это системное приложение, весит оно 50 КБ и удалять его там также НЕ РЕКОМЕНДУЮТ. Но если оч хочется удалить, то вот пишут люди что можете попробовать его заморозить из Link2SD (что это такое я увы не знаю) и потом протестировать пару дней, не будет ли проблем?
Вот нашел такую картинку но не знаю относится ли она к ProxyHandler или не относится:

ВОТ РЕБЯТА, нашел еще одно подтверждение тому что это СИСТЕМНОЕ ПРИЛОЖЕНИЕ:

Кстати я еще узнал, что файл приложения лежит тут (но что это значит я не знаю):
Ну все ребята, я как археолог искал везде инфу, рылся в этом интернете и русские сайты смотрел и английские, но все что я нашел, то все это я вам тут изложил. Увы, но инфы больше я не нашел, извините!
Proxy и Reflect
Объект Proxy «оборачивается» вокруг другого объекта и может перехватывать (и, при желании, самостоятельно обрабатывать) разные действия с ним, например чтение/запись свойств и другие. Далее мы будем называть такие объекты «прокси».
Прокси используются во многих библиотеках и некоторых браузерных фреймворках. В этой главе мы увидим много случаев применения прокси в решении реальных задач.
let proxy = new Proxy(target, handler);
- target – это объект, для которого нужно сделать прокси, может быть чем угодно, включая функции.
- handler – конфигурация прокси: объект с «ловушками» («traps»): методами, которые перехватывают разные операции, например, ловушка get – для чтения свойства из target , ловушка set – для записи свойства в target и так далее.
При операциях над proxy , если в handler имеется соответствующая «ловушка», то она срабатывает, и прокси имеет возможность по-своему обработать её, иначе операция будет совершена над оригинальным объектом target .
В качестве начального примера создадим прокси без всяких ловушек:
let target = <>; let proxy = new Proxy(target, <>); // пустой handler proxy.test = 5; // записываем в прокси (1) alert(target.test); // 5, свойство появилось в target! alert(proxy.test); // 5, мы также можем прочитать его из прокси (2) for(let key in proxy) alert(key); // test, итерация работает (3)
Так как нет ловушек, то все операции на proxy применяются к оригинальному объекту target .
- Запись свойства proxy.test= устанавливает значение на target .
- Чтение свойства proxy.test возвращает значение из target .
- Итерация по proxy возвращает значения из target .
Как мы видим, без ловушек proxy является прозрачной обёрткой над target .
Proxy – это особый, «экзотический», объект, у него нет собственных свойств. С пустым handler он просто перенаправляет все операции на target .
Чтобы активировать другие его возможности, добавим ловушки.
Что именно мы можем ими перехватить?
Для большинства действий с объектами в спецификации JavaScript есть так называемый «внутренний метод», который на самом низком уровне описывает, как его выполнять. Например, [[Get]] – внутренний метод для чтения свойства, [[Set]] – для записи свойства, и так далее. Эти методы используются только в спецификации, мы не можем обратиться напрямую к ним по имени.
Ловушки как раз перехватывают вызовы этих внутренних методов. Полный список методов, которые можно перехватывать, перечислен в спецификации Proxy, а также в таблице ниже.
Для каждого внутреннего метода в этой таблице указана ловушка, то есть имя метода, который мы можем добавить в параметр handler при создании new Proxy , чтобы перехватывать данную операцию:
| Внутренний метод | Ловушка | Что вызывает |
|---|---|---|
| [[Get]] | get | чтение свойства |
| [[Set]] | set | запись свойства |
| [[HasProperty]] | has | оператор in |
| [[Delete]] | deleteProperty | оператор delete |
| [[Call]] | apply | вызов функции |
| [[Construct]] | construct | оператор new |
| [[GetPrototypeOf]] | getPrototypeOf | Object.getPrototypeOf |
| [[SetPrototypeOf]] | setPrototypeOf | Object.setPrototypeOf |
| [[IsExtensible]] | isExtensible | Object.isExtensible |
| [[PreventExtensions]] | preventExtensions | Object.preventExtensions |
| [[DefineOwnProperty]] | defineProperty | Object.defineProperty, Object.defineProperties |
| [[GetOwnProperty]] | getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor, for..in , Object.keys/values/entries |
| [[OwnPropertyKeys]] | ownKeys | Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in , Object.keys/values/entries |
Инварианты
JavaScript налагает некоторые условия – инварианты на реализацию внутренних методов и ловушек.
Большинство из них касаются возвращаемых значений:
- Метод [[Set]] должен возвращать true , если значение было успешно записано, иначе false .
- Метод [[Delete]] должен возвращать true , если значение было успешно удалено, иначе false .
- …и так далее, мы увидим больше в примерах ниже.
Есть и другие инварианты, например:
- Метод [[GetPrototypeOf]] , применённый к прокси, должен возвращать то же значение, что и метод [[GetPrototypeOf]] , применённый к оригинальному объекту. Другими словами, чтение прототипа объекта прокси всегда должно возвращать прототип оригинального объекта.
Ловушки могут перехватывать вызовы этих методов, но должны выполнять указанные условия.
Инварианты гарантируют корректное и последовательное поведение конструкций и методов языка. Полный список инвариантов можно найти в спецификации, хотя скорее всего вы не нарушите эти условия, если только не соберётесь делать что-то совсем уж странное.
Теперь давайте посмотрим, как это всё работает, на реальных примерах.
Значение по умолчанию с ловушкой «get»
Чаще всего используются ловушки на чтение/запись свойств.
Чтобы перехватить операцию чтения, handler должен иметь метод get(target, property, receiver) .
Он срабатывает при попытке прочитать свойство объекта, с аргументами:
- target – это оригинальный объект, который передавался первым аргументом в конструктор new Proxy ,
- property – имя свойства,
- receiver – если свойство объекта является геттером, то receiver – это объект, который будет использован как this при его вызове. Обычно это сам объект прокси (или наследующий от него объект). Прямо сейчас нам не понадобится этот аргумент, подробнее разберём его позже.
Давайте применим ловушку get , чтобы реализовать «значения по умолчанию» для свойств объекта.
Например, сделаем числовой массив, так чтобы при чтении из него несуществующего элемента возвращался 0 .
Обычно при чтении из массива несуществующего свойства возвращается undefined , но мы обернём обычный массив в прокси, который перехватывает операцию чтения свойства из массива и возвращает 0 , если такого элемента нет:
let numbers = [0, 1, 2]; numbers = new Proxy(numbers, < get(target, prop) < if (prop in target) < return target[prop]; >else < return 0; // значение по умолчанию >> >); alert( numbers[1] ); // 1 alert( numbers[123] ); // 0 (нет такого элемента)
Как видно, это очень легко сделать при помощи ловушки get .
Мы можем использовать Proxy для реализации любой логики возврата значений по умолчанию.
Представим, что у нас есть объект-словарь с фразами на английском и их переводом на испанский:
let dictionary = < 'Hello': 'Hola', 'Bye': 'Adiós' >; alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome'] ); // undefined
Сейчас, если фразы в dictionary нет, при чтении возвращается undefined . Но на практике оставлять фразы непереведёнными лучше, чем использовать undefined . Поэтому давайте сделаем так, чтобы при отсутствии перевода возвращалась оригинальная фраза на английском вместо undefined .
Чтобы достичь этого, обернём dictionary в прокси, перехватывающий операцию чтения:
let dictionary = < 'Hello': 'Hola', 'Bye': 'Adiós' >; dictionary = new Proxy(dictionary, < get(target, phrase) < // перехватываем чтение свойства в dictionary if (phrase in target) < // если перевод для фразы есть в словаре return target[phrase]; // возвращаем его >else < // иначе возвращаем непереведённую фразу return phrase; >> >); // Запросим перевод произвольного выражения в словаре! // В худшем случае оно не будет переведено alert( dictionary['Hello'] ); // Hola alert( dictionary['Welcome to Proxy']); // Welcome to Proxy (нет перевода)
Прокси следует использовать везде вместо target
Пожалуйста, обратите внимание: прокси перезаписывает переменную:
dictionary = new Proxy(dictionary, . );
Прокси должен заменить собой оригинальный объект повсюду. Никто не должен ссылаться на оригинальный объект после того, как он был проксирован. Иначе очень легко запутаться.
Валидация с ловушкой «set»
Допустим, мы хотим сделать массив исключительно для чисел. Если в него добавляется значение иного типа, то это должно приводить к ошибке.
Ловушка set срабатывает, когда происходит запись свойства.
set(target, property, value, receiver) :
- target – это оригинальный объект, который передавался первым аргументом в конструктор new Proxy ,
- property – имя свойства,
- value – значение свойства,
- receiver – аналогично ловушке get , этот аргумент имеет значение, только если свойство – сеттер.
Ловушка set должна вернуть true , если запись прошла успешно, и false в противном случае (будет сгенерирована ошибка TypeError ).
Давайте применим её для проверки новых значений:
let numbers = []; numbers = new Proxy(numbers, < // (*) set(target, prop, val) < // для перехвата записи свойства if (typeof val == 'number') < target[prop] = val; return true; >else < return false; >> >); numbers.push(1); // добавилось успешно numbers.push(2); // добавилось успешно alert("Длина: " + numbers.length); // 2 numbers.push("тест"); // TypeError (ловушка set на прокси вернула false) alert("Интерпретатор никогда не доходит до этой строки (из-за ошибки в строке выше)");
Обратите внимание, что встроенная функциональность массива по-прежнему работает! Значения добавляются методом push . Свойство length при этом увеличивается. Наш прокси ничего не ломает.
Нам не нужно переопределять методы массива push и unshift и другие, чтобы добавлять туда проверку на тип, так как внутри себя они используют операцию [[Set]] , которая перехватывается прокси.
Таким образом, код остаётся чистым и прозрачным.
Не забывайте вернуть true
Как сказано ранее, нужно соблюдать инварианты.
Для set реализация ловушки должна возвращать true в случае успешной записи свойства.
Если забыть это сделать или возвратить любое ложное значение, это приведёт к ошибке TypeError .
Перебор при помощи «ownKeys» и «getOwnPropertyDescriptor»
Object.keys , цикл for..in и большинство других методов, которые работают со списком свойств объекта, используют внутренний метод [[OwnPropertyKeys]] (перехватываемый ловушкой ownKeys ) для их получения.
Такие методы различаются в деталях:
- Object.getOwnPropertyNames(obj) возвращает не-символьные ключи.
- Object.getOwnPropertySymbols(obj) возвращает символьные ключи.
- Object.keys/values() возвращает не-символьные ключи/значения с флагом enumerable (подробнее про флаги свойств было в главе Флаги и дескрипторы свойств).
- for..in перебирает не-символьные ключи с флагом enumerable , а также ключи прототипов.
…Но все они начинают с этого списка.
В примере ниже мы используем ловушку ownKeys , чтобы цикл for..in по объекту, равно как Object.keys и Object.values пропускали свойства, начинающиеся с подчёркивания _ :
let user = < name: "Вася", age: 30, _password: "***" >; user = new Proxy(user, < ownKeys(target) < return Object.keys(target).filter(key =>!key.startsWith('_')); > >); // ownKeys исключил _password for(let key in user) alert(key); // name, затем: age // аналогичный эффект для этих методов: alert( Object.keys(user) ); // name,age alert( Object.values(user) ); // Вася,30
Как видно, работает.
Впрочем, если мы попробуем возвратить ключ, которого в объекте на самом деле нет, то Object.keys его не выдаст:
let user = < >; user = new Proxy(user, < ownKeys(target) < return ['a', 'b', 'c']; >>); alert( Object.keys(user) ); //
Почему? Причина проста: Object.keys возвращает только свойства с флагом enumerable . Для того, чтобы определить, есть ли этот флаг, он для каждого свойства вызывает внутренний метод [[GetOwnProperty]] , который получает его дескриптор. А в данном случае свойство отсутствует, его дескриптор пуст, флага enumerable нет, поэтому оно пропускается.
Чтобы Object.keys возвращал свойство, нужно либо чтобы свойство в объекте физически было, причём с флагом enumerable , либо перехватить вызовы [[GetOwnProperty]] (это делает ловушка getOwnPropertyDescriptor ), и там вернуть дескриптор с enumerable: true .
Вот так будет работать:
let user = < >; user = new Proxy(user, < ownKeys(target) < // вызывается 1 раз для получения списка свойств return ['a', 'b', 'c']; >, getOwnPropertyDescriptor(target, prop) < // вызывается для каждого свойства return < enumerable: true, configurable: true /* . другие флаги, возможно, "value: . " */ >; > >); alert( Object.keys(user) ); // a, b, c
Ещё раз заметим, что получение дескриптора нужно перехватывать только если свойство отсутствует в самом объекте.
Защищённые свойства с ловушкой «deleteProperty» и другими
Существует широко распространённое соглашение о том, что свойства и методы, название которых начинается с символа подчёркивания _ , следует считать внутренними. К ним не следует обращаться снаружи объекта.
Однако технически это всё равно возможно:
let user = < name: "Вася", _password: "secret" >; alert(user._password); // secret
Давайте применим прокси, чтобы защитить свойства, начинающиеся на _ , от доступа извне.
Нам будут нужны следующие ловушки:
- get – для того, чтобы сгенерировать ошибку при чтении такого свойства,
- set – для того, чтобы сгенерировать ошибку при записи,
- deleteProperty – для того, чтобы сгенерировать ошибку при удалении,
- ownKeys – для того, чтобы исключить такие свойства из for..in и методов типа Object.keys .
Вот соответствующий код:
let user = < name: "Вася", _password: "***" >; user = new Proxy(user, < get(target, prop) < if (prop.startsWith('_')) < throw new Error("Отказано в доступе"); >else < let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) >>, set(target, prop, val) < // перехватываем запись свойства if (prop.startsWith('_')) < throw new Error("Отказано в доступе"); >else < target[prop] = val; return true; >>, deleteProperty(target, prop) < // перехватываем удаление свойства if (prop.startsWith('_')) < throw new Error("Отказано в доступе"); >else < delete target[prop]; return true; >>, ownKeys(target) < // перехватываем попытку итерации return Object.keys(target).filter(key =>!key.startsWith('_')); > >); // "get" не позволяет прочитать _password try < alert(user._password); // Error: Отказано в доступе >catch(e) < alert(e.message); >// "set" не позволяет записать _password try < user._password = "test"; // Error: Отказано в доступе >catch(e) < alert(e.message); >// "deleteProperty" не позволяет удалить _password try < delete user._password; // Error: Отказано в доступе >catch(e) < alert(e.message); >// "ownKeys" исключает _password из списка видимых для итерации свойств for(let key in user) alert(key); // name
Обратите внимание на важную деталь в ловушке get на строке (*) :
get(target, prop) < // . let value = target[prop]; return (typeof value === 'function') ? value.bind(target) : value; // (*) >
Зачем для функции вызывать value.bind(target) ?
Всё дело в том, что метод самого объекта, например user.checkPassword() , должен иметь доступ к свойству _password :
user = < // . checkPassword(value) < // метод объекта должен иметь доступ на чтение _password return value === this._password; >>
Вызов user.checkPassword() получает проксированный объект user в качестве this (объект перед точкой становится this ), так что когда такой вызов обращается к this._password , ловушка get вступает в действие (она срабатывает при любом чтении свойства), и выбрасывается ошибка.
Поэтому мы привязываем контекст к методам объекта – оригинальный объект target в строке (*) . Тогда их дальнейшие вызовы будут использовать target в качестве this , без всяких ловушек.
Такое решение обычно работает, но не является идеальным, поскольку метод может передать оригинальный объект куда-то ещё, и возможна путаница: где изначальный объект, а где – проксированный.
К тому же, объект может проксироваться несколько раз (для добавления различных возможностей), и если передавать методу исходный, то могут быть неожиданности.
Так что везде использовать такой прокси не стоит.
Приватные свойства в классах
Современные интерпретаторы JavaScript поддерживают приватные свойства в классах. Названия таких свойств должны начинаться с символа # . Они подробно описаны в главе Приватные и защищённые методы и свойства. Для них не нужны подобные прокси.
Впрочем, приватные свойства имеют свои недостатки. В частности, они не наследуются.
«В диапазоне» с ловушкой «has»
Давайте посмотрим ещё примеры.
Предположим, у нас есть объект range , описывающий диапазон:
let range = < start: 1, end: 10 >;
Мы бы хотели использовать оператор in , чтобы проверить, что некоторое число находится в указанном диапазоне.
Ловушка has перехватывает вызовы in .
- target – это оригинальный объект, который передавался первым аргументом в конструктор new Proxy ,
- property – имя свойства
let range = < start: 1, end: 10 >; range = new Proxy(range, < has(target, prop) < return prop >= target.start && prop >); alert(5 in range); // true alert(50 in range); // false
Отлично выглядит, не правда ли? И очень просто в реализации.
Оборачиваем функции: «apply»
Мы можем оборачивать в прокси и функции.
Ловушка apply(target, thisArg, args) активируется при вызове прокси как функции:
- target – это оригинальный объект (как мы помним, функция – это объект в языке JavaScript),
- thisArg – это контекст this .
- args – список аргументов.
Например, давайте вспомним декоратор delay(f, ms) , созданный нами в главе Декораторы и переадресация вызова, call/apply.
Тогда мы обошлись без создания прокси. Вызов delay(f, ms) возвращал функцию, которая передавала вызовы f после ms миллисекунд.
Вот предыдущая реализация, на основе функции:
function delay(f, ms) < // возвращает обёртку, которая вызывает функцию f через таймаут return function() < // (*) setTimeout(() =>f.apply(this, arguments), ms); >; > function sayHi(user) < alert(`Привет, $!`); > // после обёртки вызовы sayHi будут срабатывать с задержкой в 3 секунды sayHi = delay(sayHi, 3000); sayHi("Вася"); // Привет, Вася! (через 3 секунды)
Как мы уже видели, это в целом работает. Функция-обёртка в строке (*) вызывает нужную функцию с указанной задержкой.
Но наша функция-обёртка не перенаправляет операции чтения/записи свойства и другие. После обёртывания доступ к свойствам оригинальной функции, таким как name , length , и другим, будет потерян.
function delay(f, ms) < return function() < setTimeout(() =>f.apply(this, arguments), ms); >; > function sayHi(user) < alert(`Привет, $!`); > alert(sayHi.length); // 1 (в функции length - это число аргументов в её объявлении) sayHi = delay(sayHi, 3000); alert(sayHi.length); // 0 (в объявлении функции-обёртки ноль аргументов)
Прокси куда более мощные в этом смысле, поскольку они перенаправляют всё к оригинальному объекту.
Давайте используем прокси вместо функции-обёртки:
function delay(f, ms) < return new Proxy(f, < apply(target, thisArg, args) < setTimeout(() =>target.apply(thisArg, args), ms); > >); > function sayHi(user) < alert(`Привет, $!`); > sayHi = delay(sayHi, 3000); alert(sayHi.length); // 1 (*) прокси перенаправляет чтение свойства length на исходную функцию sayHi("Вася"); // Привет, Вася! (через 3 секунды)
Результат такой же, но сейчас не только вызовы, но и другие операции на прокси перенаправляются к оригинальной функции. Таким образом, операция чтения свойства sayHi.length возвращает корректное значение в строке (*) после проксирования.
Мы получили лучшую обёртку.
Существуют и другие ловушки: полный список есть в начале этой главы. Использовать их можно по аналогии с вышеописанными.
Reflect
Reflect – встроенный объект, упрощающий создание прокси.
Ранее мы говорили о том, что внутренние методы, такие как [[Get]] , [[Set]] и другие, существуют только в спецификации, что к ним нельзя обратиться напрямую.
Объект Reflect делает это возможным. Его методы – минимальные обёртки вокруг внутренних методов.
Вот примеры операций и вызовы Reflect , которые делают то же самое:
| Операция | Вызов Reflect | Внутренний метод |
|---|---|---|
| obj[prop] | Reflect.get(obj, prop) | [[Get]] |
| obj[prop] = value | Reflect.set(obj, prop, value) | [[Set]] |
| delete obj[prop] | Reflect.deleteProperty(obj, prop) | [[Delete]] |
| new F(value) | Reflect.construct(F, value) | [[Construct]] |
| … | … | … |
let user = <>; Reflect.set(user, 'name', 'Вася'); alert(user.name); // Вася
В частности, Reflect позволяет вызвать операторы ( new , delete …) как функции ( Reflect.construct , Reflect.deleteProperty , …). Это интересная возможность, но здесь нам важно другое.
Для каждого внутреннего метода, перехватываемого Proxy , есть соответствующий метод в Reflect , который имеет такое же имя и те же аргументы, что и у ловушки Proxy .
Поэтому мы можем использовать Reflect , чтобы перенаправить операцию на исходный объект.
В этом примере обе ловушки get и set прозрачно (как будто их нет) перенаправляют операции чтения и записи на объект, при этом выводя сообщение:
let user = < name: "Вася", >; user = new Proxy(user, < get(target, prop, receiver) < alert(`GET $`); return Reflect.get(target, prop, receiver); // (1) >, set(target, prop, val, receiver) < alert(`SET $=$`); return Reflect.set(target, prop, val, receiver); // (2) > >); let name = user.name; // выводит "GET name" user.name = "Петя"; // выводит "SET name=Петя"
- Reflect.get читает свойство объекта.
- Reflect.set записывает свойство и возвращает true при успехе, иначе false .
То есть, всё очень просто – если ловушка хочет перенаправить вызов на объект, то достаточно вызвать Reflect. с теми же аргументами.
В большинстве случаев мы можем сделать всё то же самое и без Reflect , например, чтение свойства Reflect.get(target, prop, receiver) можно заменить на target[prop] . Но некоторые нюансы легко упустить.
Прокси для геттера
Рассмотрим конкретный пример, демонстрирующий, чем лучше Reflect.get , и заодно разберёмся, зачем в get/set нужен третий аргумент receiver , мы его ранее не использовали.
Допустим, у нас есть объект user со свойством _name и геттером для него.
Сделаем вокруг user прокси:
let user = < _name: "Гость", get name() < return this._name; >>; let userProxy = new Proxy(user, < get(target, prop, receiver) < return target[prop]; >>); alert(userProxy.name); // Гость
Ловушка get здесь «прозрачная», она возвращает свойство исходного объекта и больше ничего не делает. Для нашего примера этого вполне достаточно.
Казалось бы, всё в порядке. Но давайте немного усложним пример.
Если мы унаследуем от проксированного user объект admin , то мы увидим, что он ведёт себя некорректно:
let user = < _name: "Гость", get name() < return this._name; >>; let userProxy = new Proxy(user, < get(target, prop, receiver) < return target[prop]; // (*) target = user >>); let admin = < __proto__: userProxy, _name: "Админ" >; // Ожидается: Админ alert(admin.name); // выводится Гость (. )
Обращение к свойству admin.name должно возвращать строку "Админ" , а выводит "Гость" !
В чём дело? Может быть, мы делаем что-то не так с наследованием?
Но если убрать прокси, то всё будет работать как ожидается.
На самом деле, проблема в прокси, в строке (*) .
- При чтении admin.name , так как в объекте admin нет свойства name , оно ищется в прототипе.
- Прототипом является прокси userProxy .
- При чтении из прокси свойства name срабатывает ловушка get и возвращает его из исходного объекта как target[prop] в строке (*) . Вызов target[prop] , если prop – это геттер, запускает его код в контексте this=target . Поэтому результатом является this._name из исходного объекта target , то есть из user .
Именно для исправления таких ситуаций нужен receiver , третий аргумент ловушки get . В нём хранится ссылка на правильный контекст this , который нужно передать геттеру. В данном случае это admin .
Как передать геттеру контекст? Для обычной функции мы могли бы использовать call/apply , но это же геттер, его не вызывают, просто читают значение.
Это может сделать Reflect.get . Всё будет работать верно, если использовать его.
Вот исправленный вариант:
let user = < _name: "Гость", get name() < return this._name; >>; let userProxy = new Proxy(user, < get(target, prop, receiver) < // receiver = admin return Reflect.get(target, prop, receiver); // (*) >>); let admin = < __proto__: userProxy, _name: "Админ" >; alert(admin.name); // Админ
Сейчас receiver , содержащий ссылку на корректный this (то есть на admin ), передаётся геттеру посредством Reflect.get в строке (*) .
Можно переписать ловушку и короче:
get(target, prop, receiver)
Методы в Reflect имеют те же названия, что и соответствующие ловушки, и принимают такие же аргументы. Это было специально задумано при разработке спецификации JavaScript.
Так что return Reflect. даёт простую и безопасную возможность перенаправить операцию на оригинальный объект и при этом предохраняет нас от возможных ошибок, связанных с этим действием.
Ограничения прокси
Прокси – уникальное средство для настройки поведения объектов на самом низком уровне. Но они не идеальны, есть некоторые ограничения.
Встроенные объекты: внутренние слоты
Многие встроенные объекты, например Map , Set , Date , Promise и другие используют так называемые «внутренние слоты».
Это как свойства, но только для внутреннего использования в самой спецификациии. Например, Map хранит элементы во внутреннем слоте [[MapData]] . Встроенные методы обращаются к слотам напрямую, не через [[Get]]/[[Set]] . Таким образом, прокси не может перехватить их.
Почему это имеет значение? Они же всё равно внутренние!
Есть один нюанс. Если встроенный объект проксируется, то в прокси не будет этих «внутренних слотов», так что попытка вызвать на таком прокси встроенный метод приведёт к ошибке.
let map = new Map(); let proxy = new Proxy(map, <>); proxy.set('test', 1); // будет ошибка
Внутри себя объект типа Map хранит все данные во внутреннем слоте [[MapData]] . Прокси не имеет такого слота. Встроенный метод Map.prototype.set пытается получить доступ к своему внутреннему свойству this.[[MapData]] , но так как this=proxy , то не может его найти и завершается с ошибкой.
К счастью, есть способ исправить это:
let map = new Map(); let proxy = new Proxy(map, < get(target, prop, receiver) < let value = Reflect.get(. arguments); return typeof value == 'function' ? value.bind(target) : value; >>); proxy.set('test', 1); alert(proxy.get('test')); // 1 (работает!)
Сейчас всё сработало, потому что get привязывает свойства-функции, такие как map.set , к оригинальному объекту map . Таким образом, когда реализация метода set попытается получить доступ к внутреннему слоту this.[[MapData]] , то всё пройдёт благополучно.
Объект Array не использует внутренние слоты
Важным исключением является встроенный объект Array : он не использует внутренние слоты. Так сложилось исторически, ведь массивы были добавлены в язык очень давно.
То есть описанная выше проблема не возникает при проксировании массивов.
Приватные поля
Нечто похожее происходит и с приватными полями классов.
Например, метод getName() осуществляет доступ к приватному полю #name , после проксирования он перестаёт работать:
class User < #name = "Гость"; getName() < return this.#name; >> let user = new User(); user = new Proxy(user, <>); alert(user.getName()); // Ошибка
Причина всё та же: приватные поля реализованы с использованием внутренних слотов. JavaScript не использует [[Get]]/[[Set]] при доступе к ним.
В вызове getName() значением this является проксированный user , в котором нет внутреннего слота с приватными полями.
Решением, как и в предыдущем случае, является привязка контекста к методу:
class User < #name = "Гость"; getName() < return this.#name; >> let user = new User(); user = new Proxy(user, < get(target, prop, receiver) < let value = Reflect.get(. arguments); return typeof value == 'function' ? value.bind(target) : value; >>); alert(user.getName()); // Гость
Однако, такое решение имеет ряд недостатков, о которых уже говорилось: методу передаётся оригинальный объект, который может быть передан куда-то ещё, и это может поломать всю функциональность проксирования.
Прокси != оригинальный объект
Прокси и объект, который проксируется, являются двумя разными объектами. Это естественно, не правда ли?
Если мы используем оригинальный объект как ключ, а затем проксируем его, то прокси не будет найден:
let allUsers = new Set(); class User < constructor(name) < this.name = name; allUsers.add(this); >> let user = new User("Вася"); alert(allUsers.has(user)); // true user = new Proxy(user, <>); alert(allUsers.has(user)); // false
Как мы видим, после проксирования не получается найти объект user внутри множества allUsers , потому что прокси – это другой объект.
Прокси не перехватывают проверку на строгое равенство ===
Прокси способны перехватывать много операторов, например new (ловушка construct ), in (ловушка has ), delete (ловушка deleteProperty ) и так далее.
Но нет способа перехватить проверку на строгое равенство. Объект строго равен только самому себе, и никаким другим значениям.
Так что все операции и встроенные классы, которые используют строгую проверку объектов на равенство, отличат прокси от изначального объекта. Прозрачной замены в данном случае не произойдёт.
Отключаемые прокси
Отключаемый (revocable) прокси – это прокси, который может быть отключён вызовом специальной функции.
Допустим, у нас есть какой-то ресурс, и мы бы хотели иметь возможность закрыть к нему доступ в любой момент.
Для того, чтобы решить поставленную задачу, мы можем использовать отключаемый прокси, без ловушек. Такой прокси будет передавать все операции на проксируемый объект, и у нас будет возможность в любой момент отключить это.
let = Proxy.revocable(target, handler)
Вызов возвращает объект с proxy и функцией revoke , которая отключает его.
let object = < data: "Важные данные" >; let = Proxy.revocable(object, <>); // передаём прокси куда-нибудь вместо оригинального объекта. alert(proxy.data); // Важные данные // позже в коде revoke(); // прокси больше не работает (отключён) alert(proxy.data); // Ошибка
Вызов revoke() удаляет все внутренние ссылки на оригинальный объект из прокси, так что между ними больше нет связи, и оригинальный объект теперь может быть очищен сборщиком мусора.
Мы можем хранить функцию revoke в WeakMap , чтобы легко найти её по объекту прокси:
let revokes = new WeakMap(); let object = < data: "Важные данные" >; let = Proxy.revocable(object, <>); revokes.set(proxy, revoke); // ..позже в коде.. revoke = revokes.get(proxy); revoke(); alert(proxy.data); // Ошибка (прокси отключён)
Преимущество такого подхода в том, что мы не должны таскать функцию revoke повсюду. Мы получаем её при необходимости из revokes по объекту прокси.
Мы использовали WeakMap вместо Map , чтобы не блокировать сборку мусора. Если прокси объект становится недостижимым (то есть на него больше нет ссылок), то WeakMap позволяет сборщику мусора удалить его из памяти вместе с соответствующей функцией revoke , которая в этом случае больше не нужна.
Ссылки
- Спецификация: Proxy, Reflect.
- MDN: Proxy, Reflect.
Итого
Прокси – это обёртка вокруг объекта, которая «по умолчанию» перенаправляет операции над ней на объект, но имеет возможность перехватывать их.
Проксировать можно любой объект, включая классы и функции.
let proxy = new Proxy(target, < /* ловушки */ >);
…Затем обычно используют прокси везде вместо оригинального объекта target . Прокси не имеет собственных свойств или методов. Он просто перехватывает операцию, если имеется соответствующая ловушка, а иначе перенаправляет её сразу на объект target .
Мы можем перехватывать:
- Чтение ( get ), запись ( set ), удаление ( deleteProperty ) свойства (даже несуществующего).
- Вызов функции ( apply ).
- Оператор new (ловушка construct ).
- И многие другие операции (полный список приведён в начале статьи, а также в документации).
Это позволяет нам создавать «виртуальные» свойства и методы, реализовывать значения по умолчанию, наблюдаемые объекты, функции-декораторы и многое другое.
Мы также можем оборачивать один и тот же объект много раз в разные прокси, добавляя ему различные аспекты функциональности.
Reflect API создано как дополнение к Proxy. Для любой ловушки из Proxy существует метод в Reflect с теми же аргументами. Нам следует использовать его, если нужно перенаправить вызов на оригинальный объект.
Прокси имеют некоторые ограничения:
- Встроенные объекты используют так называемые «внутренние слоты», доступ к которым нельзя проксировать. Однако, ранее в этой главе был показан один способ, как обойти это ограничение.
- То же самое можно сказать и о приватных полях классов, так как они реализованы на основе слотов. То есть вызовы проксированных методов должны иметь оригинальный объект в качестве this , чтобы получить к ним доступ.
- Проверка объектов на строгое равенство === не может быть перехвачена.
- Производительность: конкретные показатели зависят от интерпретатора, но в целом получение свойства с помощью простейшего прокси занимает в несколько раз больше времени. В реальности это имеет значение только для некоторых «особо нагруженных» объектов.
Задачи
Ошибка при чтении несуществующего свойства
Обычно при чтении несуществующего свойства из объекта возвращается undefined .
Создайте прокси, который генерирует ошибку при попытке прочитать несуществующее свойство.
Это может помочь обнаружить программные ошибки пораньше.
Напишите функцию wrap(target) , которая берёт объект target и возвращает прокси, добавляющий в него этот аспект функциональности.
Вот как это должно работать:
let user = < name: "John" >; function wrap(target) < return new Proxy(target, < /* ваш код */ >); > user = wrap(user); alert(user.name); // John alert(user.age); // Ошибка: такого свойства не существует
