A proxy is an object that allows you to intercept the execution of operations on an object and override its behavior.
The Proxy() constructor is used to create a Proxy object :
const proxy = new Proxy(target, handler);
The proxy constructor takes two parameters:
target- the purpose of creating a proxy, it can be any object to which the proxy is applied
handler- another object that determines exactly which operations of the target object will be intercepted and redefined and how exactly.
Consider the simplest example:
// object to which the proxy is applied const target = {name: "Tom"}; // object that determines how target will be redefined const handler = {}; // proxy object const proxy = new Proxy(target, handler); console.log(proxy.name); // Tom
So, in the example above target, this is the object to which proxying will be applied. In this case, this object has a name.
const target = {name: "Tom"};
Next, an empty handler is created:
const handler = {};
In principle, this object should determine how the target object will be redefined. But for now, let’s leave it blank.
Then we create a Proxy object by passing the objects to its target constructor handler.
const proxy = new Proxy(target, handler);
Proxying an object (in this case, an object target) means that through a proxy we can access the functionality of this object. And in this case, through the proxy object, we can access the property of the same proxied object target:
console.log(proxy.name); // Tom
And because we’ve used an empty handler that doesn’t override anything, the proxy essentially behaves like the original target object.
Overriding object functionality
Above, we performed the proxying of the object, but so far we have not redefined its behavior in any way. The key in this case is the definition of the handler, which can intercept calls to the properties of the proxied object. This handler can define two methods: get and set .
get method and getting object properties
The get method intercepts calls to the property when getting its value and returns some value for this property:
const handler = { get: function(target, prop, receiver) { return some_value; } };
The get method has three parameters:
target: the proxied object itself. Thanks to this parameter, we can access the functionality of the original object
prop: name of the property being accessed
receiver: Proxy object through which proxying is performed
Let’s take the following example:
const target = {name: "Tom"}; const handler = { get: function(target, prop, receiver) { return "Tomas Smith"; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Tomas Smith
Here, in the handler handler in the method get, the string “Tomas Smith” is returned:
get: function(target, prop, receiver) { return "Tomas Smith"; }
This will lead to the fact that when accessing any property of the proxy object, this string will be returned:
console.log(proxy.name); // Tomas Smith
So, we performed the interception of a property call and the simplest redefinition. In this case, we can also communicate with the original object that is being proxied:
const target = {name: "Tom"}; const handler = { get: function(target, prop) { return "Name: " + target.name; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Name: Tom
Here, the handler returns a string “Name: ” + target.name, where it target.name represents a call to the original object’s name property. Naturally, the logic for returning a property value can be more complex.
But let’s take a more complex object – with two properties:
const target = {name: "Tom", age: 37}; const handler = { get: function(target, prop) { return target[prop]; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Tom console.log(proxy.age); // 37
Here the target object has two properties: name and age. In the handler, we intercept the access to them, but do not override it in any way, but simply return the values of the properties of the original object:
return target[prop];
Array syntax is used to access the properties of the target object.
But we can also check which property is being accessed and perform some redefinition:
const target = {name: "Tom", age: 37}; const handler = { get: function(target, prop) { if(prop==="name") return target.name.toUpperCase(); else return target[prop]; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // TOM console.log(proxy.age); // 37
In this case, if the call goes to the property name, that is, to the property that stores the string, then we call the method on this string toUpperCase() and translate it into upper case.
Property setting and set method
The set method intercepts calls to the property when setting its value:
const handler = { set: function(target, property, value, receiver) { } };
The set method has four parameters:
- target: original object to be proxied to
- property: name of the property being accessed
- value: value to be set
- receiver: Proxy object through which proxying is performed
Let’s look at an example:
const target = {name: "Tom", age: 37}; const handler = { set: function(target, prop, value) { console.log(value); target[prop] = value; } }; const proxy = new Proxy(target, handler); proxy.name = "Tomas"; console.log(proxy.name); // Tomas proxy.age = 22; console.log(proxy.age); // 22
In this example, in the set method, we first log the value passed to the property, then set the property:
target[prop] = value;
Let’s change the example a bit:
const target = {name: "Tom", age: 37}; const handler = { set: function(target, prop, value) { if(prop==="age" && value < 1) console.log("Incorrect age"); else return target[prop] = value; } }; const proxy = new Proxy(target, handler); proxy.name = "Tomas"; console.log(proxy.name); // Thomas proxy.age = -199; // Incorrect age console.log(proxy.age); // 37 proxy.age = 22; console.log(proxy.age); // 22
Here, in the handler method set, we check if the property is being set age and the value is less than 1, then we simply display a message about incorrect data
if(prop==="age" && value < 1) console.log("Incorrect age");
Otherwise, we pass the value to the property of the original object:
else return target[prop] = value;