Why Proxy and Reflect in JavaScript, what is the difference and when and what to use?


What are Proxies useful for, and how often are they used in real development?

It is also unclear about the reflexes, it turns out that this is almost the same. It is not clear why both exist at the same time.

Author: Vasily, 2020-08-04

3 answers

Proxy objects are "traps" with which we can intercept the "events" we need from objects, classes, functions, etc.

If you are not familiar with them at all, then I would say that this is very similar to the eventListners from the Browser API, since with the help of Proxy we can bind and, if necessary, intercept the event we need from those entities listed above.

What does this one look like? trap:

const address = {
  country: "Russia",
  city: "Moscow"
}

const addressProxy = new Proxy(address, {
  // здесь мы определяем какое именно действие
  // по объекту address мы хотим перехватить

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

  // для этого мы поставим ловушку/handler
  get: (target, prop) => {
    return target[prop]
  }
})

console.log(addressProxy.country)
// Russia

console.log(addressProxy.city)
// Moscow

console.log(addressProxy.street)
// undefined

Now we get the values of the object, but we do it already through a proxy, so we can actually do anything with value before giving it back:

const addressProxy = new Proxy(address, {
  get: (target, prop) => {
      if (prop === "country") {
          return target[prop].slice(0, 2).toUpperCase()
      }
      return target[prop]
  }
})

console.log(addressProxy.country)
// RU

console.log(addressProxy.city)
// Moscow

console.log(addressProxy.street)
// undefined

Vol.e now we are how would you set the EventListener on the "event" get(), after it was triggered, we intercepted a request to a specific key and changed its value to the format we needed.

Now we can safely add new keys to our object:

addressProxy.street = "Arbat"

console.log(addressProxy)
// { country: "Russia", city: "Moscow", street: "Arbat" }

But it is easy to prohibit this by binding to the "event" set():

const addressProxy = new Proxy(address, {
  set: (target, prop, value) => {
      return false
  }
})

addressProxy.street = "Arbat"

console.log(city in addressProxy)
// { country: "Russia", city: "Moscow" }

We can also hide certain fields:

addressProxy.metadata = 12345

console.log(addressProxy)
// { country: "Russia", city: "Moscow", metadata: 12345 }

const addressProxy = new Proxy(address, {
  has: (target, prop) => {
      return prop in target && prop !== "metadata"
  }
})

If we now "ask" if there is such a field, we get:

console.log("country" in addressProxy)
// true

console.log("city" in addressProxy)
// true

console.log("metadata" in addressProxy)
// false

Functions can also be proxied, so we can, for example, track the moment when it is called:

const print = text => console.log(text)

const printProxy = new Proxy(print, {
  apply: (target, thisArg, argArray) => {
      return target.apply(thisArg, argArray)
  }
})

printProxy("это тест, мы успешно перехватили вызов функции")
// это тест, мы успешно перехватили вызов функции

Which will allow us to implement the following logic effortlessly:

// давайте отфильтруем плохие слова
// запретив нашей функции их вывод
// и оставим только приятный слуху язык

const print = text => console.log(text)

const printProxy = new Proxy(print, {
  apply: (target, thisArg, argArray) => {
      // для простоты примера представим что 
      // в массиве запрещенных слов сейчас 
      // только одно слово
      const badWords = ["ругательство"]
      if (badWords.includes(argArray[0])) {
        return target("***")
      }
      return target(argArray[0])
  }
})

printProxy("спасибо")
// спасибо

printProxy("ругательство")
// ***

I think that now the main idea of the proxy has become more understandable. Separately, I want to note that for each of the entities there are specific handlers, for example, for functions it is apply(), and for classes (and everything that starts with the new operator) construct().

The full list of handlers can be found here.

How often are they used in real development?

In large modern projects, proxying is used quite often, one of the common applications are all sorts of"optimizations".

Suppose we have an array of users and we need to find the right one by its ID:

const users = [
  { id: 1, name: "Иван" },
  { id: 2, name: "Мария" },
  { id: 3, name: "Антон" }
]

// что бы найти пользователя с ID равным 3
// нам нужно пройтись по всему массиву
// и сверить ID у каждого пользователя
// пока мы не найдем нужный

const targetUser = users.find(user => user.id === 3)

console.log(targetUser)
// { id: 3, name: "Антон" }

In principle, there are no problems if there are only three users, but what should we do if there are 100,000 of them?

(a permanent bulkhead of this volume will be very costly)

const users = [
  { id: 1, name: "Иван" },
  { id: 2, name: "Мария" },
  { id: 3, name: "Антон" }
  // ...
  // и еще более чем
  // 100.000 записей
]

We can query the Array class, and add a handler construct() to it, which will allow us to "bind" to when each new instance is initialized.

Inside it we iterate our array and assign each record an index equal to the user ID:

const IndexedArray = new Proxy(Array, {
  construct: () => {
    const index = {}
      users.forEach(item => index[item.id] = item)
      return index
  }
})

const indexedUsers = new IndexedArray(users)

The iteration will be performed only once, at the time of creating a new instance of IndexedArray:

console.log(indexedUsers)
// {
//   "1": { id: 1, name: "Иван" },
//   "2": { id: 2, name: "Мария" },
//   "3": { id: 3, name: "Антон" }
//   ... остальные 100.000
// }

After that, we will be able to get the user we need as easily as possible:

console.log(indexedUsers[3])
// { id: 3, name: "Антон" }

I intentionally simplified the above example so that it would be easy to understand the basic the idea, however, to keep the full functionality, including adding/removing / changing fields, etc. it will need to be finalized.


Proxy and Reflect 's are standard built-in objects, but if the first is intended to "intercept" and" rewrite "the fundamental operations of the proxied object, the second provides methods for working with" intercepted " operations.

All methods and properties The Reflect ' s are static, and the object itself is non-functional, which means that it is non-constructible and we will not be able to use it together with the operator new or call it as a function.

The names of the functions of the Reflect object have names identical to the names of the handlers in the Proxy, and some of them repeat the function of the methods of the Object class, although with some differences.

Why is this necessary?

This is standardization.

For example, the apply() method is present in all constructors (many implementations), and with its output in Reflect (one implementation).

Default settings ESLint already signal that instead of:

testFunction.apply(thisArg, argsArr)

Worth using:

Reflect.apply(testFunction, thisArg, argsArr)

So, for example, instead of writing:

const chinaAddress = new Address(argsArr)
// sidenote
// такой подход приведет к созданию 
// и использованию итератора

You can write it like this:

const chinaAddress = Reflect.construct(Address, argsArr)
// sidenote
// такой подход не потребует задействования итератора
// поскольку construct() использует
// length и прямой доступ
// что в целом положительно повлияет на оптимизацию

That is, choosing Reflect instead of standard methods, we just go on the same wave in the direction of where the modern JavaScipt is moving.

 12
Author: Vasily, 2020-08-13 10:23:13

Proxy - this is a constructor that allows you to make a wrapper over an object, in which you can override the standard behavior, for example, accessing the object property

var target = {
  a: 1
};

var handler = {
  get(target, propertyName, proxy) {
    return target.hasOwnProperty(propertyName) ? target[propertyName] : 42;
  }
}


var proxy = new Proxy(target, handler);

console.log(proxy.a);
console.log(proxy.b);
console.log(target.b);

As you can see in the example, when accessing a property that is missing from the target object, the pre-specified value will be returned, not undefined.


Reflect - this is a object that provides methods for working with an object that is duplicated in it some methods from Object, for example

Object.defineProperty - Reflect.defineProperty

As well as methods that duplicate the functionality of some operators,

So in this object, we decided to collect methods for working with the internals of the object.


Proxy actively uses Vuejs

 6
Author: Grundy, 2020-08-04 10:13:34

These are experimental methods, perhaps they will be removed later. In real projects, I have never met them. In general, a proxy is one of the classic programming patterns, but js is somehow used to doing without it.

Https://learn.javascript.ru/proxy

 -1
Author: Arturas Lapinskas, 2020-08-04 09:56:51