Главная » Статьи » ES6 в действии: Как использовать прокси

ES6 в действии: Как использовать прокси

ES6 в действии: Как использовать прокси

От автора: в вычислительных науках прокси — это то, что находится между вами и чем-то, с чем вы взаимодействуете. Этот термин чаще всего применяется к прокси-серверу — устройству между веб-браузером (Chrome, Firefox, Safari, Edge и т. д.) и веб-сервером (Apache, Nginx, IIS и т. д.), на котором размещена страница. Прокси-сервер может изменять запросы и ответы. Например, он может повысить эффективность за счет кэширования часто запрашиваемых ресурсов и предоставления их нескольким пользователям.

В ES прокси расположены между вашим кодом и объектом. Прокси позволяет выполнять операции метапрограммирования, такие как перехват вызова для проверки или изменения свойства объекта. В отношении прокси ES6 используется следующая терминология:

цель

Исходный объект, который прокси-сервер будет виртуализировать. Это может быть объект JavaScript, такой как библиотека jQuery или собственные объекты, такие как массивы или даже другие прокси.

обработчик

Объект, который реализует поведение прокси, используя …

захват

Функции, определенные в обработчике, которые обеспечивают доступ к цели при вызове определенных свойств или методов.
Это лучше всего объяснить на простом примере. Мы создадим объект цели с именем target, который содержит три свойства:

const target = { a: 1, b: 2, c: 3
};

Далее мы создадим объект-обработчик, который перехватывает все операции get. Этот код возвращает свойство цели, когда оно доступно, или 42 в противном случае:

const handler = { get: function(target, name) { return ( name in target ? target[name] : 42 ); }
};

Теперь мы создаем новый прокси, передавая объекты цели и обработчика. Наш код может взаимодействовать с прокси, а не напрямую обращаться к объекту target:

const proxy = new Proxy(target, handler); console.log(proxy.a); // 1
console.log(proxy.b); // 2
console.log(proxy.c); // 3
console.log(proxy.meaningOfLife); // 42

Давайте расширим обработчик прокси, чтобы он позволял устанавливать только односимвольные свойства от a до z:

const handler = { get: function(target, name) { return (name in target ? target[name] : 42); }, set: function(target, prop, value) { if (prop.length == 1 && prop >= 'a' && prop <= 'z') { target[prop] = value; return true; } else { throw new ReferenceError(prop + ' cannot be set'); return false; } }
}; const proxy = new Proxy(target, handler); proxy.a = 10;
proxy.b = 20;
proxy.ABC = 30;
// Исключение: ReferenceError: ABC cannot be set

Типы прокси-захватов

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

construct(target, argList) — Захватывает создание нового объекта с помощью оператора new.

get(target, property) — Захватывает Object.get() и должен вернуть значение свойства.

set(target, property, value) — Захватывает Object.set()и должен установить значение свойства. Возвращает true, если все прошло успешно. В строгом режиме возврат false приведет к возникновению исключения TypeError.

deleteProperty(target, property) — Захватывает операцию удаления объекта. Должен вернуть true или false.

apply(target, thisArg, argList) — Вызов функции объекта захвата.

has(target, property) — Захватывает операторы in и должен вернуть либо true, либо false.

ownKeys(target) — Захватывает Object.getOwnPropertyNames() и должен вернуть перебираемый объект.

getPrototypeOf(target) — Захватывает Object.getPrototypeOf() и должен вернуть объект прототипа или ноль.

setPrototypeOf(target, prototype) — Захватывает Object.setPrototypeOf(), чтобы установить объект прототипа. Значения не возвращаются.

isExtensible(target) — Захватывает Object.isExtensible(), который определяет, может ли объект содержать новые добавляемые свойства. Должен вернуть либо true, либо false.

preventExtensions(target) — Захватывает Object.preventExtensions(), который предотвращает добавление новых свойств в объект. Должен вернуть либо true, либо false.

getOwnPropertyDescriptor(target, property) — Захватывает Object.getOwnPropertyDescriptor(), который возвращает undefined или объект дескриптора свойства с атрибутами для значения, writable, get, set, configurable и enumerable.

defineProperty(target, property, descriptor) — Захватывает Object.defineProperty(), который определяет или модифицирует свойство объекта. Должен вернуть true, если свойство цели было успешно определено, или в противном случае — false.

Пример прокси 1: Профилирование

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

// создаем прокси профилирования
function makeProfiler(target) { const count = {}, handler = { get: function(target, name) { if (name in target) { count[name] = (count[name] || 0) + 1; return target[name]; } } }; return { proxy: new Proxy(target, handler), count: count }
};

Теперь мы можем применить эту прокси-оболочку к любому объекту или другому прокси. Например:

const myObject = { h: 'Hello', w: 'World'
}; // создаем прокси myObject
const pObj = makeProfiler(myObject); // получаем доступ к свойствам
console.log(pObj.proxy.h); // Hello
console.log(pObj.proxy.h); // Hello
console.log(pObj.proxy.w); // World
console.log(pObj.count.h); // 2
console.log(pObj.count.w); // 1

Хотя это простой пример, представьте, как сложно было бы выполнить эти операции, если бы вам приходилось выполнять подсчет количества доступов к ресурсам в нескольких разных объектах без использования прокси.

Пример прокси 2: Двусторонняя привязка данных

Привязка данных синхронизирует объекты. Она обычно используется в MVC-библиотеках JavaScript для обновления внутреннего объекта при изменении DOM и наоборот. Предположим, у нас есть поле ввода с идентификатором inputname:

<input type="text" id="inputname" value="" />

У нас также есть объект JavaScript с именем myUser с свойством id, которое связано с вводимыми данными:

// внутреннее состояние для поля #inputname
const myUser = { id: 'inputname', name: ''
};

Наша первая цель — обновить myUser.name, когда пользователь меняет вводимое значение. Этого можно добиться с помощью обработчика событий для поля onchange:

inputChange(myUser); // привязываем вводимые данные к объекту
function inputChange(myObject) { if (!myObject || !myObject.id) return; const input = document.getElementById(myObject.id); input.addEventListener('onchange', function(e) { myObject.name = input.value; });
}

Наша следующая задача — обновить поле ввода, когда мы модифицируем в JavaScript-коде myUser.name. Это не так просто сделать, но прокси предоставляют необходимое решение:

// обработчик прокси
const inputHandler = { set: function(target, prop, newValue) { if (prop == 'name' && target.id) { // обновляем свойство объекта target[prop] = newValue; // обновляем значение поля ввода document.getElementById(target.id).value = newValue; return true; } else return false; }
} // создаем прокси
const myUserProxy = new Proxy(myUser, inputHandler); // задаем новое имя
myUserProxy.name = 'Craig';
console.log(myUserProxy.name); // Craig
console.log(document.getElementById('inputname').value); // Craig

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

Поддержка прокси

Преимущества прокси могут быть на первый взгляд неочевидны, но они предлагают мощные возможности метапрограммирования. Брендан Эйч, создатель JavaScript, считает, что Прокси — это потрясающе!

В настоящее время поддержка прокси реализована в Node и во всех современных браузерах, за исключением Internet Explorer 11. Однако обратите внимание, что не все браузеры поддерживают все захваты. Вы можете узнать подробнее о том, что именно поддерживается с помощью этой таблицы совместимости с браузерами на странице MDN по прокси.

К сожалению, невозможно полифиллировать или транспилировать код прокси ES6 с помощью таких инструментов, как Babel, поскольку прокси выполняют много функций и не имеют эквивалента в ES5.

Автор: Craig Buckler

Источник: https://www.sitepoint.com/

Редакция: Команда webformyself.