Why you need Proxy objects in JavaScript

JavaScript is not truly Object Oriented language, hence implementing requirements as stated below could be challenging. For example,

  • Throw an error if the setting value of a property does not satisfy a particular condition
  • If the property does not exist on set some default value instead of undefined
  • If the property does not exist on reading operation throw TypeError
  • Create a private property on the object
  • Enforce property value validation
  • Enforcing value correct on the property

There are many ways some of the above stated problems can be solved, such that by using setter or getter to set default value or throwing an error or using an object’s methods such that isExtensible(), preventExtension(, etc to avoid adding dynamic properties etc. However, the more convenient way to handle the above problems is by using the Proxy object.

You can watch video on the proxy object here:

The Proxy object is a virtualizing interface to control the behavior of the object.  To understand the proxy in a better way, let us consider Product object as shown in below code listing:


let Product = {
    id:1,
    price : 78
  }

 

The Product object has two properties id and price. Now let us say that you want to run a business logic that price should not be less than 50, or reading value of the property, which does not exist, should throw error, etc.

You can solve most of the above problems by running custom logic while performing property lookup, set, get, and enumeration operations on the object. JavaScript proxy object exactly helps here. It allows you to add custom behavior to the fundamental operations on a JavaScript object.

po

 

The syntax to create a Proxy object is as shown in the below image:

po1

You create a proxy object using the Proxy constructor and passing two parameters in it.

  1. Target
  2. Handler

Target is the object, which proxy virtualizes and adds custom behavior to it. In our example, the Product object is the target as we are adding custom behavior to it.

The handler is the object, in which you write the custom behavior. In more technical words, the handler object contains the trap.

The traps are the methods, which gives the target object’s property access inside the handler object. Traps are optional, and if not provided target object’s default methods are used.   There are traps such as to get, set, has, setPrototypeOf, isExtensible, defineProperty, etc. You can use all of these traps in the handler do define custom behavior for the target object.

Let us apply a custom behavior on the Product object that, a read operation on the property that does not exist should throw an error.  You can create a proxy for that as shown in the below code listing :


var ProductHandler = {
      get:function(obj,prop){
          return prop in obj ?
          obj[prop] : new TypeError(prop + ' - property does not exist');
      }
  };

var productProxy = new Proxy(Product,ProductHandler);

 

Let us walk through the code; we created a proxy called productProxy using the Proxy constructor and in the Proxy constructor passing,

  1. The Product object as the target
  2. The ProductHanlder object as the handler.

The ProductHandler is using the get trap, in which it is checking if property exists in the object or not. If it does not exist, return a TypeError with the message.  Now read properties value as shown in code listed below:


console.log(productProxy.price);
console.log(productProxy.color); 

 

Since price property exists, you get 78 value printed, however when you try to read property color, which does not exist, JavaScript throws you an error as shown in the image below:

po3.png

The better use case of proxy is in validation. Using the proxy, a validation can be enforced on the property value.  Let us consider the code listed below,


let Product = {
    id:1,
    price : 78
  }

Product.price = -10; 
console.log(Product.price); // -10

We can set prices to a negative value. Using the proxy, we can avoid it by enforcing value validations, as shown in the code listed below:


var ProductHandler = {
    set : function(obj, prop,value){
        if(prop == 'price'){
            if( !Number.isInteger(value)){
                throw new TypeError('passed value is not a number');
            }
            if(value < 50){
                obj[prop]= 50;
                return true; 
            }
        }
        obj[prop]= value;
        return true; 
    }
};

var productProxy = new Proxy(Product,ProductHandler);

 

To enforce value validation handler object is using the set trap, in which it is

  1. Checking for the property price
  2. Verifying whether the passed value is an integer or not
  3. If the passed value is for property price is less than 50, setting the value to 50

Now when you set price to a negative value due to defined custom behavior in the proxy object JavaScript will not allow that, and you will get 50 printed for second read operation as shown in code listed below,


console.log(productProxy.price); // 78
productProxy.price = -10; 
console.log(productProxy.price); // 50

 

You can enforce id as the private property of the Product object as shown in the code listed below:


var ProductHandler = {
    get:function(obj,prop){

        if(prop == 'id'){
            throw new Error('Cannot access private property : id!');
        }
        else {
            return prop in obj ?
            obj[prop] : new TypeError (prop + ' - property does not exist');
        }
    }
};

var productProxy = new Proxy(Product,ProductHandler);

 

Here, in the get trap of the handler, we check if the property being accessed is the id property, and if so, handler throws an error. Otherwise, it returns the value as usual.

JavaScript Proxy objects are useful and allow us to add custom behavior to a normal object.  You can use it for various purposes such as validation, creating access modifier, setting default values, etc.  I hope you find this post useful. Thanks for reading.

For any Training or Consulting reach me at debugmode[at]outlook.com