JavaScript Proxy

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;