Proxies are commonly applied in various libraries and several browser frameworks.
A Proxy object is used for wrapping another object and intercepting operations such as writing and reading properties, dealing with them, or transparently making the object to handle them.
Proxy
The first thing to know is the syntax of proxy, which looks as follows:
let proxy = new Proxy(target, handler)
This syntax includes a target and a handler. The target is the object that is going to be wrapped. It can be functions, and so on. The handler is the configuration of the proxy.
In case there is a trap in the handler, then it runs, and the proxy can handle it. In another way, the operation will be implemented on the target.
As a starting point, let’s consider an example of a proxy without traps:

In case there isn’t any trap, the operations are passed to the target. It is necessary to add traps for activating more capabilities.
In JavaScript, there is a so-called “internal method” for most object operations, describing how it works at the lowest level. For example, the [[Get]] method is used for reading a property, [[Set]] - for writing a property. Anyway, such kinds of methods can not be called by name.
Further, we will cover operations with some of the methods and traps. JavaScript imposes invariants: those are conditions that must be carried out by the internal methods and traps.
For example,[[Set]] , [[Delete]] , and others return values. In the case of using [[Delete]], it should return true once the value was deleted, and, otherwise, it will return false.
Other types of variants also exist. For example, when [[GetPrototypeOf]] is inserted to the proxy object, it must return the same value as [[GetPrototypeOf]], attached to the target object of the proxy.
Default Value with “get” Trap
The most frequently used traps are for writing and reading properties. For intercepting reading, the handler should include a get method. It occurs once a property is read, along with the target (the target object), property (the property name), and the receiver (if the target property is a getter, then the object is the receiver).
For using get to perform default values for an object, it is necessary to make a numeric array, which returns 0 for nonexistent values.
As a rule, trying to get a non-existing array item leads to undefined. However, you can wrap a regular array into the proxy that returns 0, like here:

Validation with “set” Trap
Imagine that you need to get an array numbers. Adding another value type will cause an error.
The set trap occurs when a property is written. It includes target, property, value, and receiver.
So, the set trap returns true when the setting is done successfully, and false, otherwise.
p>It can be used for validating new values, like this:
Also consider that the built-in functionality of arrays still works. With push, it is possible to add values.
Value-adding methods such as push and unshift shouldn’t be overridden.
Protected Properties with “deleteProperty” and Other Traps
It is known that properties and methods that are prefixed by an underscore _ are considered internal. It is not possible to access them outside the object.
Let’s see how it is possible technically:

So, proxies are used for preventing any access to the properties that begin with _.
The necessary traps are as follows:
- get - for throwing an error while reading a property like that.
- set - for throwing an error while writing.
- deleteProperty - for throwing an error while deleting.
- ownKeys - for excluding properties that begin with _ from for..in and methods such as Object.keys.
The code will look like this:

In the line (*), there is an important detail:
get(target, prop) {
// ...
let val = target[prop];
return (typeof val === 'function') ? val.bind(target) : val; // (*)
}
So, a function is necessary to call value.bind(target) because object methods like user.checkPassword() should be able to access _password, like this:
user = {
// ...
checkPassword(val) {
// object method should be able to read _userPassword
return val === this._userPassword;
}
}
Wrapping Functions: "apply"
It is possible to wrap a proxy around a function, too.
The oapply(target, thisArg, args) trap can handle calling a proxy as a function. It includes target (the target object), thisArg (the value of this), and args (a list of arguments).
For instance, imagine recalling the delay(fn, ms) decorator. It returns a function, which forwards all the calls to fn after ms milliseconds.
The function-based performance will look like this:

It is noticeable that the wrapper function (*) makes the call after a timeout. But the wrapper function isn’t capable of forwarding write and read operations, and so on. And, when the wrapping is implemented, the access to the properties will be lost:

Luckily, the proxy can forward anything to the target object. So, it is recommended to use proxy rather than the wrapping function. Here is an example of using the proxy:

Reflect
Reflect is the term used for specifying a built-in object, which simplifies the proxy creation.
As it was noted above, the internal methods such as [[Set]],[[Get]] , and others can’t be called directly. Reflect can be used for making it possible.
Let’s see an example of using reflect:

For each internal method, that is trapped by proxy, there is a matching method in reflect. It has the same name and arguments as the proxy trap.
So, reflect is used for forwarding an operation to the original object.
Let’s check out another example:

In this example, an object property is read by Reflect.get. Also, Reflect.set writes an object property, returning true if successful, and false, otherwise.
Proxy Limitations
Of course, proxies are a unique and handy way for altering or tweaking the behavior of the existing objects at the lowest level. But, it can’t handle anything and has limitations. Below, we will look through the main proxy limitations.
Built-in objects: internal slots
Most of the built-in objects such as Set, Map, Date, and so on work with internal slots.
Those are similar to properties but reserved for internal purposes. For instance, Map embraces items in the internal slot [[MapData]] . The built-in methods are capable of accessing them directly and not through the [[Get]]/[[Set]] methods. So, it can’t be intercepted by the proxy.
An essential issue here is that after a built-in object gets proxied, the proxy doesn’t include the slots, so an error occurs, like this:

Let’s see another case. A Map encloses all the data in the [[MapData]] internal slot. Then, the Map.prototype.set method attempts to access the internal this.[[MapData]]. As a result, it will not be found in the proxy.
But there is a way of fixing it:

Please, take into consideration a notable exception: the built-in Array never uses internal slots.
Private Fields
Something similar takes place with private fields. For instance, the getName() method can access the #name and break after proxying, like this:

That happens because private fields are performed by applying internal slots. While accessing them, JavaScript doesn’t use [[Get]]/[[Set]] .
Calling getName() the proxied user is the value of this . It doesn’t have the slot with private fields.
Let’s have a look at the solution, which will make it work:

Proxy != target
The original object and the proxy are considered different objects.
Having the original object as a key, proxying it, then it won’t be found, like here:

It is noticeable that the user can’t be found in the allUsers after proxying. That’s because it’s a different object.
Please, also note that proxies don’t intercept various operators like new, delete and more. All the operations and built-in classes comparing objects for equality, differentiate between the proxy and the object.
Revocable Proxies
A proxy that can be disabled is called a revocable proxy.
Imagine having a resource and want to close access to it at any time. It is necessary to wrap it to a revocable proxy without using traps. A proxy like that will forward operations to object. It can be disabled at any moment.
The syntax is the following:
let {
proxy,
revoke
} = Proxy.revocable(target, handler)
Now, let’s see an example of the call returning an object with the proxy and revoke function for disabling it:

Calling revoke() deletes all the internal references to the target object. So, there is not connection between them.
Also, revoke can be stored in WeakMap. That makes it easier to detect it using a proxy object, like this:

The primary benefit here is that it’s not necessary to carry revoke anymore. To sum up, we can state that a Proxy is a wrapper, surrounding an object, which forwards operations on it to the object. Some of them can optionally by trapped. Any type of object can be wrapped, including functions and classes.
Proxy doesn’t include own methods and properties. It will trap the operation when there is a trap. Otherwise, it will be forwarded to target the object.
To complement proxy, an API, called reflect is created. For each proxy trap, you can find a corresponding reflect, using the same arguments.