Статьи

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

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

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

Следующая терминология используется в отношении прокси 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 ); } }; 

Теперь мы создаем новый Proxy, передавая объекты target и handler. Наш код может взаимодействовать с прокси, а не напрямую обращаться к 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; // Exception: ReferenceError: ABC cannot be set 

Типы ловушек прокси

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

  • конструкция (target, argList)
    Прерывает создание нового объекта с помощью new оператора.
  • получить (цель, свойство)
    Traps Object.get() и должен возвращать значение свойства.
  • установить (цель, свойство, значение)
    Traps Object.set() и должен установить значение свойства. Верните true если успешно. В строгом режиме возвращение false вызовет исключение TypeError.
  • deleteProperty (цель, свойство)
    Пропускает операцию delete в свойстве объекта. Должен возвращать либо true либо false .
  • применить (target, thisArg, argList)
    Ловит вызовы функций объекта.
  • имеет (цель, свойство)
    Ловушки in операторах и должны возвращать либо true либо false .
  • ownKeys (цель)
    Traps Object.getOwnPropertyNames() и должен возвращать перечисляемый объект.
  • getPrototypeOf (цель)
    Traps Object.getPrototypeOf() и должен возвращать объект прототипа или нуль.
  • setPrototypeOf (цель, прототип)
    Traps Object.setPrototypeOf() для установки объекта-прототипа. Никакое значение не возвращается.
  • isExtensible (цель)
    Traps Object.isExtensible() , который определяет, могут ли у объекта быть добавлены новые свойства. Должен возвращать либо true либо false .
  • preventExtensions (цель)
    Traps Object.preventExtensions() , который предотвращает добавление новых свойств к объекту. Должен возвращать либо true либо false .
  • getOwnPropertyDescriptor (цель, свойство)
    Traps Object.getOwnPropertyDescriptor() , который возвращает неопределенный объект или объект дескриптора свойства с атрибутами для value , writable , get , set , configurable и enumerable .
  • defineProperty (цель, свойство, дескриптор)
    Traps Object.defineProperty() который определяет или изменяет свойство объекта. Должен возвращать true если целевое свойство было успешно определено, или false если нет.

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

Прокси позволяют вам создавать универсальные обертки для любого объекта без необходимости изменять код внутри самих целевых объектов.

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

 // create a profiling 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' }; // create a myObject proxy const pObj = makeProfiler(myObject); // access properties 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: двустороннее связывание данных

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

Предположим, у нас есть поле ввода с идентификатором inputname :

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

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

 // internal state for #inputname field const myUser = { id: 'inputname', name: '' }; 

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

 inputChange(myUser); // bind input to object function inputChange(myObject) { if (!myObject || !myObject.id) return; const input = document.getElementById(myObject.id); input.addEventListener('onchange', function(e) { myObject.name = input.value; }); } 

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

 // proxy handler const inputHandler = { set: function(target, prop, newValue) { if (prop == 'name' && target.id) { // update object property target[prop] = newValue; // update input field value document.getElementById(target.id).value = newValue; return true; } else return false; } } // create proxy const myUserProxy = new Proxy(myUser, inputHandler); // set a new name myUserProxy.name = 'Craig'; console.log(myUserProxy.name); // Craig console.log(document.getElementById('inputname').value); // Craig 

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

Дальнейшие примеры

В статье Hemanth.HM Negative Array Index в JavaScript предлагается использовать прокси для реализации отрицательных индексов массивов. Например, arr[-1] возвращает последний элемент, arr[-2] возвращает предпоследний элемент и т. Д.

Статья Николаса Закаса Создание безопасных по типу свойств с помощью прокси-серверов ECMAScript 6 иллюстрирует, как можно использовать прокси-серверы для обеспечения безопасности типов путем проверки новых значений. В приведенном выше примере мы могли убедиться, что myUserProxy.name всегда было установлено в строку, и в противном случае myUserProxy.name ошибку.

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

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

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

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