Рубрики
Без рубрики

3 способа использования прокси ES6 для улучшения ваших объектов

В этой статье я хочу охватить три вещи, которые вы можете сделать с прокси, которые специально улучшат ваши объекты. Надеемся, что к концу этого вы сможете расширить мой код и, возможно, применить его самостоятельно к своим нуждам! Tagged с JavaScript, Node, Proxy, WebDev.

Первоначально опубликовано по адресу: https://blog.logrocket.com/use-es6-proxies-to-enhance-your-objects/

Одним из аспектов программирования, который я люблю больше всего, является метапрограммирование, которое ссылается на способность менять основные строительные блоки языка, используя сам язык для внесения изменений. Разработчики используют эту технику для улучшения языка или даже, в некоторых случаях, для создания новых пользовательских языков, известных как Домен конкретный язык (или DSL для короткого).

Многие языки уже обеспечивают глубокие уровни метапрограммирование , но в JavaScript отсутствовал некоторые ключевые аспекты.

Да, это правда, JavaScript достаточно гибкий, что позволяет вам немного растянуть язык, учитывая, как вы можете добавить атрибуты в объект во время выполнения, или как вы можете легко улучшить поведение функции, передавая его по-разному функции как параметр. Но со всем этим были все еще некоторые ограничения, которые новые прокси теперь позволяют нам превзойти.

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

Как работают прокси? Быстрое вступление

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

Ловушки, с которыми мы можем поиграть:

GetPrototypeof Запускается при вызове метода с тем же именем на своем собственном объекте.
SetPrototypeof То же самое, что и раньше, но для этого конкретного метода.
Isextensible Запускается, когда мы пытаемся понять, может ли объект быть расширен (т. Е. Получить новые свойства, добавленные к нему во время выполнения).
Предотвращение То же, что и раньше, но для этого конкретного метода (который, кстати, он игнорировал любые новые свойства, которые вы добавляете в объект во время выполнения).
GetownPropertyDescriptor Этот метод обычно возвращает объект дескриптора для свойства данного объекта. Эта ловушка запускается при использовании метода.
DefineProperty Выполнено, когда этот метод вызывается.
имеет Запускается, когда мы используем оператор в IN (например, когда мы это делаем, если («значение» в массиве)). Это очень интересно, поскольку вы не ограничиваете добавление этой ловушки для массивов, вы также можете расширить другие объекты.
получить Довольно просто, запускается при попытке получить доступ к значению свойства (т. Е. YourObject.Prop).
набор То же самое, что и выше, но запускается при установлении значения на свойство.
DeleteProperty По сути, ловушка, вызванная при использовании оператора Delete.
Собственные Запускается при использовании методов GetownPropertyNames и GetownPropertySymbols на своем объекте.
применять Запускается при вызове функции. Мы будем уделять много внимания этому, вы просто ждете.
конструкция Запускается при создании нового объекта с новым оператором.

Это стандартные ловушки, вы более чем можете проверить Веб -документы Мозиллы Для получения более подробной информации о каждом из них, так как я сосредотачиваюсь на подстановлении из них для этой статьи.

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

let myString = new String("hi there!")
let myProxiedVar = new Proxy(myString, {
  has: function(target, key) {
    return target.indexOf(key) != -1;
  }
})
console.log("i" in myString)
// false
console.log("i" in myProxiedVar)
//true

Это основа прокси, я буду показывать более сложные примеры за секунду, но все они основаны на одном и том же синтаксисе.

Прокси против отражения

Но прежде чем мы начнем смотреть на примеры, я хотел быстро охватить этот вопрос, так как он много спрашивает. С ES6 мы не просто получили прокси, мы также получили [Отражается] (https://developer.mozilla.org/en-us/docs/web/javascript/reference/global_objects/reflect) объект , что на первый взгляд делает то же самое , не так ли?

Основная путаница возникает из -за того, что большая часть документации указывает, что Отражать имеет те же методы, что и прокси -обработчики, которые мы видели выше (то есть ловушки). И хотя это правда, там есть отношения 1: 1, поведение Отражать Объект и его методы более похожи на методы Объект Глобальный объект.

Например, следующий код:

const object1 = {
  x: 1,
  y: 2
};

console.log(Reflect.get(object1, 'x'));

Вернут 1, как будто вы бы прямо пытались получить доступ к собственности. Таким образом, вместо изменения ожидаемого поведения вы можете просто выполнить его с помощью другого (и в некоторых случаях, более динамическом) синтаксиса.

Улучшение № 1: динамический доступ к свойствам

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

Под этим я подразумеваю, что у вас есть объект, такой как:

class User {
  constructor(fname, lname) {
    this.firstname =  fname
    this.lastname = lname
  }
}

Вы можете легко получить имя или фамилию, но вы не можете просто запросить полное имя одновременно. Или, если вы хотите получить имя во всех крышках, вам придется подключить вызовы методов. Это ни в коем случае не проблема, вот как вы делаете это в JavaScript:

let u = new User("fernando", "doglio")
console.log(u.firstname + " " + u.lastname)
//would yield: fernando doglio
console.log(u.firstname.toUpperCase())
//would yield: FERNANDO

Но с прокси, есть способ сделать ваш код более декларативным. Подумайте об этом, что, если бы вы могли сделать ваши заявления поддержки объектов, такие как:

let u = new User("fernando", "doglio")
console.log(u.firstnameAndlastname)
//would yield: fernando doglio
console.log(u.firstnameInUpperCase)
//would yield: FERNANDO

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

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

Вот код, который может позволить нам сделать именно это:

function EnhanceGet(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {

      if(target.hasOwnProperty(prop)) {
          return target[prop]
      }
      let regExp = /([a-z0-9]+)InUpperCase/gi
      let propMatched = regExp.exec(prop)

      if(propMatched) {
        return target[propMatched[1]].toUpperCase()
      } 

      let ANDRegExp = /([a-z0-9]+)And([a-z0-9]+)/gi
      let propsMatched = ANDRegExp.exec(prop)
      if(propsMatched) {
          return [target[propsMatched[1]], target[propsMatched[2]]].join(" ")
      }
      return "not found"
     }
  });
}

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

Теперь вы можете использовать этот прокси с любым собственным объектом, и Getter Getter будет улучшен!

Усовершенствование № 2: Пользовательская обработка ошибок для недействительных имен свойств

Затем у нас есть еще одно маленькое, но интересное улучшение. Всякий раз, когда вы пытаетесь получить доступ к свойству, которого не существует на объекте, вы на самом деле не получаете ошибки, JavaScript является таким разрешающим. Все, что вы получаете, это неопределенное вернулся вместо его значения.

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

Мы могли бы вполне хорошо использовать прокси для этого, вот как:

function CustomErrorMsg(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      if(target.hasOwnProperty(prop)) {
          return target[prop]
      }
      return new Error("Sorry bub, I don't know what a '" + prop + "' is...")
     }
  });
}

Теперь этот код вызовет следующее поведение:

> pa = CustomErrorMsg(a)
> console.log(pa.prop)
Error: Sorry bub, I don't know what a 'prop' is...
    at Object.get (repl:7:14)
    at repl:1:16
    at Script.runInThisContext (vm.js:91:20)
    at REPLServer.defaultEval (repl.js:317:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:615:10)
    at REPLServer.emit (events.js:187:15)
    at REPLServer.EventEmitter.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:290:10)

Мы могли бы быть более экстремальными, как я упоминал, и сделать что -то вроде:

function HardErrorMsg(obj) {
  return new Proxy(obj, {
    get(target, prop, receiver) {
      if(target.hasOwnProperty(prop)) {
          return target[prop]
      }
      throw new Error("Sorry bub, I don't know what a '" + prop + "' is...")
     }
  });
}

И теперь мы заставляем разработчиков быть более внимательными при использовании ваших объектов:

> a = {}
> pa2 = HardErrorMsg(a)
> try {
... console.log(pa2.property)
 } catch(e) {
... console.log("ERROR Accessing property: ", e)
 }
ERROR Accessing property:  Error: Sorry bub, I don't know what a 'property' is...
    at Object.get (repl:7:13)
    at repl:2:17
    at Script.runInThisContext (vm.js:91:20)
    at REPLServer.defaultEval (repl.js:317:29)
    at bound (domain.js:396:14)
    at REPLServer.runBound [as eval] (domain.js:409:12)
    at REPLServer.onLine (repl.js:615:10)
    at REPLServer.emit (events.js:187:15)
    at REPLServer.EventEmitter.emit (domain.js:442:20)
    at REPLServer.Interface._onLine (readline.js:290:10)

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

Вы можете многое сделать, используя основное поведение, показанное выше, чтобы сформировать JavaScript для вашего конкретного желания.

Улучшение № 3: Динамическое поведение на основе имен методов

Последний пример, который я хочу рассказать, похож на первый. Независимо от того, смогли ли мы добавить дополнительную функциональность, используя название свойства для цепочки дополнительного поведения (например, с окончанием «inuppercase»), теперь я хочу сделать то же самое для вызовов методов. Это позволило бы нам не только расширить поведение основных методов, просто добавив дополнительные биты к его названию, но и получить параметры, связанные с этими дополнительными битами.

Позвольте мне привести пример того, что я имею в виду:

myDbModel.findById(2, (err, model) => {
  //....
})

Этот код должен быть знаком вам, если вы использовали ORM базы данных в прошлом (например, продолжение или мангуз). Структура способна угадать, как называется ваше поле для идентификатора, основываясь на том, как вы настраиваете свои модели. Но что, если вы хотите расширить это на что -то вроде:

myDbModel.findByIdAndYear(2, 2019, (err, model) => {
  //...
})

И сделайте еще один шаг:

myModel.findByNameAndCityAndCountryId("Fernando", "La Paz", "UY", (err, model) => {
  //...
})

Мы можем использовать прокси для улучшения наших объектов в обеспечении такого поведения, позволяя нам обеспечить расширенную функциональность без необходимости вручную добавлять эти методы. Кроме того, если ваши модели БД достаточно сложны, все возможные комбинации становятся слишком большими, чтобы добавить, даже программно, наши объекты в конечном итоге в конечном итоге получат слишком много методов, которые мы просто не используем. Таким образом, мы следим за тем, чтобы у нас был только один, все, что касается всех комбинаций. В примере я собираюсь создать поддельную модель MySQL, просто используя пользовательский класс, чтобы все было просто:

var mysql      = require('mysql');
var connection = mysql.createConnection({
  host     : 'localhost',
  user     : 'user',
  password : 'pwd',
  database : 'test'
});

connection.connect();

class UserModel {
    constructor(c) {
        this.table = "users"
        this.conn = c
    }
}

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

let Enhacer = {
    get : function(target, prop, receiver) {
      let regExp = /findBy((?:And)?[a-zA-Z_0-9]+)/g
      return function() { //
          let condition = regExp.exec(prop)
          if(condition) {
            let props = condition[1].split("And")
            let query =  "SELECT * FROM " + target.table + " where " + props.map( (p, idx) => {
                let r = p + " = '" + arguments[idx] + "'"
                return r
            }).join(" AND ")
            return target.conn.query(query, arguments[arguments.length - 1])
          }
      }
    }
}

Теперь это просто обработчик, я покажу вам, как использовать его через секунду, но сначала пара очков:

  • Обратите внимание на регулярное выражение. Мы также использовали их в предыдущих примерах, но они были проще. Здесь нам нужен способ поймать повторяющуюся шаблон: findby + propname + И столько раз, сколько нам нужно.
  • С карта Позвоните, мы следим за тем, чтобы отобразить каждое имя предложения по значению, которое мы получили. И мы получаем фактическое значение, используя аргументы объект. Вот почему функция, которую мы возвращаем не может быть функцией стрелы (У них нет аргументов Мы также используем цель
  • Таблица собственность и его Conn свойство. Цель - это наш объект, как и следовало ожидать, и именно поэтому мы определили их обратно в конструкторе. Чтобы сохранить этот код общим, эти реквизиты должны прийти извне. Наконец, мы называем
  • запрос Метод с двумя параметрами, и мы предполагаем, что последний аргумент нашего поддельного метода, полученный, является фактическим обратным вызовом. Таким образом, мы просто схватим его и передаем.

Вот и все, TL; DR из вышеупомянутого: мы преобразуем имя метода в запрос SQL и выполняя его с помощью фактического запрос метод

Вот как вы бы использовали приведенный выше код:

let eModel = new Proxy(new UserModel(connection), Enhacer) //create the proxy here

eModel.findById("1", function(err, results) { //simple method call with a single parameter
    console.log(err)
    console.log(results)
})
eModel.findByNameAndId('Fernando Doglio', 1, function(err, results) { //extra parameter added
    console.log(err)
    console.log(results)
    console.log(results[0].name)
})

То есть после этого результаты используются, как и вы, ничего дополнительного не требуется.

Вывод

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

Увидимся на следующем!

Оригинал: “https://dev.to/deleteman123/3-ways-to-use-es6-proxies-to-enhance-your-objects-mfg”