Прокси (proxy) – особый объект, смысл которого – перехватывать обращения к другому объекту и, при необходимости, модифицировать их.

Синтаксис:

let proxy = new Proxy(target, handler)

Здесь:

  • target – объект, обращения к которому надо перехватывать.
  • handler – объект с «ловушками»: функциями-перехватчиками для операций к target.

Почти любая операция может быть перехвачена и обработана прокси до или даже вместо доступа к объекту target, например: чтение и запись свойств, получение списка свойств, вызов функции (если target – функция) и т.п.

Различных типов ловушек довольно много.

Сначала мы подробно рассмотрим самые важные «ловушки», а затем посмотрим и на их полный список.

Если ловушки нет – операция идёт над target

Если для операции нет ловушки, то она выполняется напрямую над target.

Самыми частыми являются ловушки для чтения и записи в объект:

get(target, property, receiver)
Срабатывает при чтении свойства из прокси. Аргументы:
  • target – целевой объект, тот же который был передан первым аргументом в new Proxy.
  • property – имя свойства.
  • receiver – объект, к которому было применено присваивание. Обычно сам прокси, либо прототипно наследующий от него. Этот аргумент используется редко.
set(target, property, value, receiver)

Срабатывает при записи свойства в прокси.

Аргументы:

  • target – целевой объект, тот же который был передан первым аргументом в new Proxy.

  • property – имя свойства.

  • value – значение свойства.

  • receiver – объект, к которому было применено присваивание, обычно сам прокси, либо прототипно наследующий от него.

    Метод set должен вернуть true, если присвоение успешно обработано и false в случае ошибки (приведёт к генерации TypeError).

Пример с выводом всех операций чтения и записи:

'use strict';

let user = {};

let proxy = new Proxy(user, {
  get(target, prop) {
    alert(`Чтение ${prop}`);
    return target[prop];
  },
  set(target, prop, value) {
    alert(`Запись ${prop} ${value}`);
    target[prop] = value;
    return true;
  }
});

proxy.firstName = "Ilya"; // запись

proxy.firstName; // чтение

alert(user.firstName); // Ilya

При каждой операции чтения и записи свойств proxy в коде выше срабатывают методы get/set. Через них значение в конечном счёте попадает в объект (или считывается из него).

Можно сделать и позаковыристее.

Методы get/set позволяют реализовать доступ к произвольным свойствам, которых в объекте нет.

Например, в коде ниже словарь dictionary содержит различные фразы:

'use strict';

let dictionary = {
  'Hello': 'Привет',
  'Bye': 'Пока'
};

alert( dictionary['Hello'] ); // Привет

А что, если фразы нет? В этом случае будем возвращать фразу без перевода и, на всякий случай, писать об этом в консоль:

'use strict';

let dictionary = {
  'Hello': 'Привет',
  'Bye': 'Пока'
};

dictionary = new Proxy(dictionary, {
  get(target, phrase) {
    if (phrase in target) {
      return target[phrase];
    } else {
      console.log(`No phrase: ${phrase}`);
      return phrase;
    }
  }
})

// Обращаемся к произвольным свойствам словаря!
alert( dictionary['Hello'] ); // Привет
alert( dictionary['Welcome']); // Welcome (без перевода)

Аналогично и перехватчик set может организовать работу с произвольными свойствами.

Ловушка has срабатывает в операторе in и некоторых других случаях, когда проверяется наличие свойства.

В примере выше, если проверить наличие свойства Welcome в dictionary, то оператор in вернёт false:

alert( 'Hello' in dictionary ); // true
alert( 'Welcome' in dictionary ); // false, нет такого свойства

Это потому, что для перехвата in используется ловушка has. При отсутствии ловушки операция производится напрямую над исходным объектом target, что и даёт такой результат.

Синтаксис has аналогичен get.

Вот так dictionary будет всегда возвращать true для любой in-проверки:

'use strict';

let dictionary = {
  'Hello': 'Привет'
};

dictionary = new Proxy(dictionary, {
  has(target, phrase) {
    return true;
  }
});

alert("BlaBlaBla" in dictionary); // true

Ловушка deleteProperty по синтаксису аналогична get/has.

Срабатывает при операции delete, должна вернуть true, если удаление было успешным.

В примере ниже delete не повлияет на исходный объект, так как все операции перехватываются и «аннигилируются» прокси:

'use strict';

let dictionary = {
  'Hello': 'Привет'
};

let proxy = new Proxy(dictionary, {
  deleteProperty(target, phrase) {
    return true; // ничего не делаем, но возвращает true
  }
});

// не удалит свойство
delete proxy['Hello'];

alert("Hello" in dictionary); // true

// будет то же самое, что и выше
// так как нет ловушки has, операция in сработает на исходном объекте
alert("Hello" in proxy); // true

Ловушка enumerate перехватывает операции for..in и for..of по объекту.

Как и ранее, если ловушки нет, то эти операторы работают с исходным объектом:

'use strict';

let obj = {a: 1, b: 1};

let proxy = new Proxy(obj, {});

// перечисление прокси работает с исходным объектом
for(let prop in proxy) {
  alert(prop); // Выведет свойства obj: a, b
}

Если же ловушка enumerate есть, то она будет вызвана с единственным аргументом target и сможет вернуть итератор для свойств.

В примере ниже прокси делает так, что итерация идёт по всем свойствам, кроме начинающихся с подчёркивания _:

'use strict';

let user = {
  name: "Ilya",
  surname: "Kantor",
  _version: 1,
  _secret: 123456
};

let proxy = new Proxy(user, {
  enumerate(target) {
    let props = Object.keys(target).filter(function(prop) {
      return prop[0] != '_';
    });

    return props[Symbol.iterator]();
  }
});

// отфильтрованы свойства, начинающиеся с _
for(let prop in proxy) {
  alert(prop); // Выведет свойства user: name, surname
}

Посмотрим внимательнее, что происходит внутри enumerate:

  1. Сначала получаем список интересующих нас свойств в виде массива.
  2. Метод должен возвратить итератор по массиву. Встроенный итератор для массива получаем через вызов props[Symbol.iterator]().

Прокси умеет работать не только с обычными объектами, но и с функциями.

Если аргумент target прокси – функция, то становится доступна ловушка apply для её вызова.

Метод apply(target, thisArgument, argumentsList) получает:

  • target – исходный объект.
  • thisArgument – контекст this вызова.
  • argumentsList – аргументы вызова в виде массива.

Она может обработать вызов сама и/или передать его функции.

'use strict';

function sum(a, b) {
  return a + b;
}

let proxy = new Proxy(sum, {
  // передаст вызов в target, предварительно сообщив о нём
  apply: function(target, thisArg, argumentsList) {
    alert(`Буду вычислять сумму: ${argumentsList}`);
    return target.apply(thisArg, argumentsList);
  }
});

// Выведет сначала сообщение из прокси,
// а затем уже сумму
alert( proxy(1, 2) );

Нечто подобное можно сделать через замыкания. Но прокси может гораздо больше. В том числе и перехватывать вызовы через new.

Ловушка construct(target, argumentsList) перехватывает вызовы при помощи new.

Она получает исходный объект target и список аргументов argumentsList.

Пример ниже передаёт операцию создания исходному классу или функции-конструктору, выводя сообщение об этом:

'use strict';

function User(name, surname) {
  this.name = name;
  this.surname = surname;
}

let UserProxy = new Proxy(User, {
  // передаст вызов new User, предварительно сообщив о нём
  construct: function(target, argumentsList) {
    alert(`Запуск new с аргументами: ${argumentsList}`);
    return new target(...argumentsList);
  }
});

let user = new UserProxy("Ilya", "Kantor");

alert( user.name ); // Ilya

Полный список возможных функций-перехватчиков, которые может задавать handler:

  • getPrototypeOf – перехватывает обращение к методу getPrototypeOf.
  • setPrototypeOf – перехватывает обращение к методу setPrototypeOf.
  • isExtensible – перехватывает обращение к методу isExtensible.
  • preventExtensions – перехватывает обращение к методу preventExtensions.
  • getOwnPropertyDescriptor – перехватывает обращение к методу getOwnPropertyDescriptor.
  • defineProperty – перехватывает обращение к методу defineProperty.
  • has – перехватывает проверку существования свойства, которая используется в операторе in и в некоторых других методах встроенных объектов.
  • get – перехватывает чтение свойства.
  • set – перехватывает запись свойства.
  • deleteProperty – перехватывает удаление свойства оператором delete.
  • enumerate – срабатывает при вызове for..in или for..of, возвращает итератор для свойств объекта.
  • ownKeys – перехватывает обращения к методу getOwnPropertyNames.
  • apply – перехватывает вызовы target().
  • construct – перехватывает вызовы new target().

Каждый перехватчик запускается с handler в качестве this. Это означает, что handler кроме ловушек может содержать и другие полезные свойства и методы.

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

Если перехватчик в handler не указан, то операция совершается, как если бы была вызвана прямо на target.

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

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

Поэтому нужна именно браузерная поддержка. Постепенно она реализуется.

Руководство Государственного академического музея им. Пушкина намерено попросить картину Леонардо да Винчи "Мона Лиза" ("Джоконда") для экспозиции, передает ТАСС.

Министр культуры Франции Франсуаз Ниссен в интервью радиостанции Europe-1 сообщил ранее, что "Джоконда" может покинуть парижский Лувр для показа в
других музеях мира.

Шедевр да Винчи после Второй мировой войны "Мона Лиза" практически все время хранился в стенах Лувра. Только в 1963 году "Мона Лиза" вывозилась в США, а в 1974 году — в Японию и СССР (Музей изобразительных искусств им. А. С. Пушкина). В 2005 году в Лувре для нее выделили отдельный зал.

Кислотный дождь впервые случился в 1852-м году.

В Хаме "Тигры" наступают, "освобождая" от ИГИЛ занятый ранее им "карман". Правда, ИГИЛ при этом никто найти не может, он категорически не желает воевать. Что неудивительно: боевики ушли с территории еще четыре дня назад, предварительно заминировав ее. Собственно, только этим и объясняется не слишком быстрое продвижение "Тигров".

При этом обозначенный на карте серо-синим цветом "карман" сирийцы пока не трогают: там находятся остатки "Хайят Тахрир аш-Шам", по поводу которых сейчас идут консультации с турками. Вполне возможно, что в духе идущей торговой войны Кремль договорится с Турцией на предмет того, чтобы выпустить ХТаШ на север. Пока торговля не закончена, сирийцам разрешено "освобождать" лишь территорию ИГ.

Все эти мероприятия пока проходят в оккупационной зоне Ирана, так что после всего на нее войдут иранские прокси и возьмут ее под контроль.

Сам ИГИЛ из Хамы, нагрузившись трофеями, полученными в сирийском "военторге" и "отжатыми" у коллег из ХТаШ, убыл в пустыню. Для него операция прошла вполне приемлемо: набег удался, боевики запаслись оружием и боеприпасами, по ряду сообщений сумели прихватить несколько бронемашин и даже пару танков, поэтому особых причин держать территорию, как Сталинград, у них нет.