Что интересного можно сделать с Proxy

внешний интерфейс JavaScript Операция

что такое прокси

Во-первых, давайте проясним,ProxyЧто это значит, слово в переводе, этоиграет роль.
Можно понять, что есть очень популярная звезда, которая открыла учетную запись Weibo.Эта учетная запись очень активна, отвечает фанатам, везде ставит лайки и т. д., но я не могу ее поддерживать на самом деле.
Но за операцией стоит еще один человек или команда, мы можем назвать их агентами, потому что публикуемый ими Weibo представляет смысл самих звезд.
P.S. Насильно приведу пример, ибо за звездами не гонюсь, просто предполагаю, что может быть такая оперативка

Это заменяется наJavaScriptсреди них можно понять, что对象или函数работа через прокси.

Прокси в JavaScript

Прокси — это новый API, представленный в ES6, который можно использовать для определения настраиваемого поведения для различных основных операций с объектами.
(упоминается в документации какtraps, думаю, это можно понимать как зацепку за разное поведение объектов)
С ним можно делать много интересных вещей, и он будет очень эффективен, когда нам нужно контролировать поведение некоторых объектов.

Прокси синтаксис

СоздаватьProxyЭкземпляр должен передавать два параметра

  1. targetПроксируемый объект, который может бытьobjectилиfunction
  2. handlersОбработка различных режимов работы прокси-объекта
let target = {}
let handlers = {} // do nothing
let proxy = new Proxy(target, handlers)

proxy.a = 123

console.log(target.a) // 123

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

Ловушки (агенты для различного поведения)

Как и в приведенном выше примере кода, если соответствующийtrap, это не будет иметь никакого эффекта, это эквивалентно прямой операцииtarget.
когда мы пишемtrapПозже, когда будет выполнено соответствующее действие, сработает наша callback-функция, и мы будем управлять поведением проксируемого объекта.

Два наиболее часто используемыхtrapдолжно бытьgetа такжеset.
ранние годаJavaScriptИмеет свойство, которое устанавливается при определении объектаgetter,setter:

let obj = {
  _age: 18,
  get age ()  {
    return `I'm ${this._age} years old`
  },
  set age (val) {
    this._age = Number(val)
  }
}

console.log(obj.age) // I'm 18 years old
obj.age = 19
console.log(obj.age) // I'm 19 years old

Как описано в этом коде, мы устанавливаем свойство_age, а затем установитеget ageа такжеset age.
Тогда мы можем напрямую вызватьobj.ageЧтобы получить возвращаемое значение, вы также можете присвоить ему значение.
Есть несколько недостатков в этом:

  1. Для каждого атрибута, подлежащего проксированию, соответствующийgetter,setter.
  2. Также должен быть магазин, в котором хранится реальная стоимостьkey(если мы прямоgetterвнутренний вызовthis.ageПереполнение стека происходит потому, что всякий раз, когда вы вызываетеthis.ageЗначение будет активированоgetter).

ProxyОн отлично решает эти две проблемы:

let target = { age: 18, name: 'Niko Bellic' }
let handlers = {
  get (target, property) {
    return `${property}: ${target[property]}`
  },
  set (target, property, value) {
    target[property] = value
  }
}
let proxy = new Proxy(target, handlers)

proxy.age = 19
console.log(target.age, proxy.age)   // 19,          age : 19
console.log(target.name, proxy.name) // Niko Bellic, name: Niko Bellic

Мы создаемget,setдваtrapЧтобы управлять всеми операциями единым образом, вы можете увидеть, что при измененииproxyТем временем,targetсодержание также было изменено, и нашproxyПоведение имеет некоторую особую обработку.
И нам не нужно использовать дополнительныйkeyчтобы сохранить реальную стоимость, потому что мы находимся вtrapВнутренняя операцияtargetобъект вместоproxyобъект.

Что делать с прокси

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

Решите проблему, что свойство объекта не определено

Как бороться с приобретением некоторых свойств объекта глубокого уровняundefinedбыл болезненным процессом, если мы используемProxyЭто может быть хорошо совместимо с этой ситуацией.

(() => {
  let target = {}
  let handlers = {
    get: (target, property) => {
      target[property] = (property in target) ? target[property] : {}
      if (typeof target[property] === 'object') {
        return new Proxy(target[property], handlers)
      }
      return target[property]
    }
  }
  let proxy = new Proxy(target, handlers)
  console.log('z' in proxy.x.y) // false (其实这一步已经针对`target`创建了一个x.y的属性)
  proxy.x.y.z = 'hello'
  console.log('z' in proxy.x.y) // true
  console.log(target.x.y.z)     // hello
})()

были представленыget, и выполнять логическую обработку внутри, если мы хотимgetЦенность исходит из несуществующегоkey, то у нас будетtargetСоздайте соответствующий в этомkey, а затем возвращает цель для этогоkeyпрокси-объект.
Это гарантирует, что наша операция со значением не выдастcan not get xxx from undefined
Но у этого есть небольшой недостаток, который заключается в том, что если вам приходится судить об этомkeyЕсть ли способ пройтиinоператор судить, а не напрямую черезgetсудить.

Совместимость Обработка обычных функций и конструкторов

если мы предоставимClassвозразить кому-то другому или сказатьES5вариант конструктора.
если не используетсяnewключевое слово для вызова,ClassОбъект выдает исключение напрямую, в то время какES5конструктор вthisУказание на него становится областью видимости при вызове функции.
мы можем использоватьapplyэтоtrapбыть совместимым с этой ситуацией:

class Test {
  constructor (a, b) {
    console.log('constructor', a, b)
  }
}

// Test(1, 2) // throw an error
let proxyClass = new Proxy(Test, {
  apply (target, thisArg, argumentsList) {
    // 如果想要禁止使用非new的方式来调用函数,直接抛出异常即可
    // throw new Error(`Function ${target.name} cannot be invoked without 'new'`)
    return new (target.bind(thisArg, ...argumentsList))()
  }
})

proxyClass(1, 2) // constructor 1 2

мы использовалиapplyЧтобы проксировать какое-то поведение, оно будет срабатывать при вызове функции, потому что мы точно знаем, что прокси — этоClassили конструктор, поэтому мы непосредственно вapplyиспользуется вnewключевое слово для вызова делегированной функции.

и если мы хотим ограничить функцию, запретить использованиеnewключевое слово для вызова, вы можете использовать другоеtrap:construct

function add (a, b) {
  return a + b
}

let proxy = new Proxy(add, {
  construct (target, argumentsList, newTarget) {
    throw new Error(`Function ${target.name} cannot be invoked with 'new'`)
  }
})

proxy(1, 2)     // 3
new proxy(1, 2) // throw an error

Обернуть выборку с помощью прокси

Отправка запросов на интерфейсе, то, что мы часто используем сейчас, должно бытьfetch, собственный API. мы можем использоватьProxyобернуть его, чтобы сделать его более удобным в использовании.

let handlers = {
  get (target, property) {
    if (!target.init) {
      // 初始化对象
      ['GET', 'POST'].forEach(method => {
        target[method] = (url, params = {}) => {
          return fetch(url, {
            headers: {
              'content-type': 'application/json'
            },
            mode: 'cors',
            credentials: 'same-origin',
            method,
            ...params
          }).then(response => response.json())
        }
      })
    }

    return target[property]
  }
}
let API = new Proxy({}, handlers)

await API.GET('XXX')
await API.POST('XXX', {
  body: JSON.stringify({name: 1})
})

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

Реализовать простой инструмент утверждения

Утверждение должен знать каждый, кто писал тест.
console.assertэто инструмент утверждения, который принимает два параметра, если первыйfalse, второй параметр будетError messageбросать.
мы можем использоватьProxyЧтобы сделать инструмент, который напрямую назначает утверждение.

let assert = new Proxy({}, {
  set (target, message, value) {
    if (!value) console.error(message)
  }
})

assert['Isn\'t true'] = false      // Error: Isn't true
assert['Less than 18'] = 18 >= 19  // Error: Less than 18

Подсчитайте количество вызовов функций

При создании сервера мы можем использоватьProxyДелегируйте некоторые функции для подсчета количества звонков за определенный период времени.
Возможно, вы сможете использовать его при анализе производительности позже:

function orginFunction () {}
let proxyFunction = new Proxy(orginFunction, {
  apply (target, thisArg. argumentsList) {
    log(XXX)

    return target.apply(thisArg, argumentsList)
  }
})

все ловушки

перечислено здесьhandlersвсе определяемое поведение(traps):

Конкретные можно посмотретьMDN-Proxy
Есть также несколько примеров

traps description
get получитьkeyстоимость
set установитьkeyстоимость
has использоватьinоператор для определенияkeyон существует
apply вызов функции, только если прокси-объектfunctionдействительный, когда
ownKeys Получить весь целевой объектkey
construct Функция вызывается инстанцированием, только если прокси-объектfunctionдействительный, когда
isExtensible Определить, является ли объект расширяемым,Object.isExtensibleагент
deleteProperty удалить одинproperty
defineProperty определить новыйproperty
getPrototypeOf получить прототип объекта
setPrototypeOf установить объект-прототип
preventExtensions установить объект как нерасширяемый
getOwnPropertyDescriptor Получить собственное имущество(не пойдет в цепочку прототипов, чтобы найти)описание свойства

использованная литература

  1. Magic Methods in JavaScript? Meet Proxy!
  2. How to use JavaScript Proxies for Fun and Profit
  3. MDN-Proxy