JavaScript has been revolutionized by the features introduced in the ES6 syntax. One of those new features which can make the lives of developers is very easy is the Proxy object. The proxy object is used to define custom execution on standard behaviors of any object. You can simply override the default standard behaviors of an object (lookup, assignment, etc).
The steps for using a proxy object are :

  1. Take an object, you want to build a proxy for.
  2. Define the custom behaviors for the proxy object and assign the object chosen in step 1 to the proxy object.
  3. Keep the real object private and expose the Proxy Object to the users.

Well, that is confusing, let us look at it with the help of an example. Suppose you have an object (let it be ob1) and every time you update a property of that object you want a custom message to be displayed. One easy solution would be to expose a setter function and return the custom message from there. But examine this solution closely, are you really forcing the user to use the getter function?  No, the user can still use the object and set the property directly using that object, without using the getter function.
This is how Proxy Object would come to rescue. When we update or set an attribute of an object (in this case ob1), we basically use the assignment or setter behavior of that object.  We create a new Proxy Object (pob1), override it’s assignment behavior to return a message whenever it is called and then associate ob1 to it. Now the object exposed for use is pob1.

Don’t worry if the concepts explained above are a bit difficult to digest, a proxy is an easy and very useful concept. Make sure you read till the end as we demystify it via some code.

Syntax:

There are three technical terms that you must be familiar with before learning the syntax of Proxy Objects.

  • Target: The object you want to create a proxy for. (the object which the proxy virtualizes) .
  • Traps: The set of overridden behaviors
  • Handler: The placeholder object which contains the traps.

Below is how you make a proxy object(handler) given the trap and target. ( We shall be using JavaScript ES6 syntax in the code, you can read about it here.)

 

const handler = {
/* All the traps are defined in here */
} 
const proxyObject = new Proxy(target, handler);

Let us now see how proxy solves the use case we discussed in the example above.

const handler = {
  get : (target, objectKey) => { 
           console.log(`Queried property is ${objectKey}`);
     return target[objectKey];
}
};

const obj = {
"val" : 1,
"msg" : 2
};

const p = new Proxy (obj, handler);
console.log (p["val"]); 
/*
Output:
Queried Property is val
1
*/ 

 

In the above example, get is a trap that is provided by ES6 to override the default access behavior. As the name says handler is the Handler (set of traps). We are making a proxy for obj, thus it is the target. And we expose  which is the proxy object for obj.

We can have multiple traps in the handler, you can find all the traps provided by JavaScript here.

 

Uses of JavaScript Proxy :

As already mentioned before, uses depends on the creativity and application of the coder. There are no limitations and no rules. What we discuss here are some of the common scenarios where Proxies are used.

  1. Validation: Set trap we can override the default behavior of setting an attribute of an object. Using the set trap we can easily enforce validations on what values can be assigned to a certain attribute of an object. You can think of it as HTML forms, wherein you can put validation on the range of values a particular form element can take. Let us take an example. in our previous case where we had an object obj with attributes val and msg, we want to enforce that the value of val attribute is always positive and single digit. Let us see how we can achieve this via proxy.
    const handler = {
      get : (target, objectKey) => { 
               console.log(`Queried property is ${objectKey}`);
         return target[objectKey];
    },
    set : (target, objectKey, value) => {
     if (objectKey === "val") {
         if (value >=10) {
            throw new TypeError("Val cannot be greater than 9")
             }  
         }
     target[objectKey] = value;
     return true; 
     }
    };
    
    const obj = {
    "val" : 1,
    "msg" : 2
    };
    
    const p = new Proxy (obj, handler);
    p.val = 90
    console.log (p["val"]); 
    /*
    Output:
    TypeError: Val cannot be greater than 9
    */ 
    
  2. Value Correction
  3. Tracing Property Access
  4. Making async call notifiers. (This is taken from a recent blog I read.)
    
    const logUpdate = require('log-update')
    const asciichart = require('asciichart')
    const chalk = require('chalk')
    const Measured = require('measured')
    const timer = new Measured.Timer()
    const history = new Array(120)
    history.fill(0)const monitor = obj => {
        return new Proxy(obj, {
          get(target, propKey) {
            const origMethod = target[propKey]
            if (!origMethod) return
            return (...args) => {
               const stopwatch = timer.start()
               const result = origMethod.apply(this, args)
               return result.then(out => {
                  const n = stopwatch.end()
                  history.shift()
                  history.push(n)
                  return out
              })
           }
        }
     })
    }
    const service = {
        callService() {
          return new Promise(resolve =>
            setTimeout(resolve, Math.random() * 50 + 50))
         }
      }
    const monitoredService = monitor(service)setInterval(() => { 
       monitoredService.callService()
       .then(() => {
           const fields = ['min', 'max', 'sum', 'variance',
                 'mean', 'count', 'median']
          const histogram = timer.toJSON().histogram
         const lines = [
      '',
      ...fields.map(field =>
      chalk.cyan(field) + ': ' +
      (histogram[field] || 0).toFixed(2))
      ]
      logUpdate(asciichart.plot(history, { height: 10 })
      + lines.join('\n'))
     })
    .catch(err => console.error(err))
    }, 100)

Conclusion

Proxies can be a very powerful tool for a developer. They give you control over every behavior of your object and allow you to customize them according to your needs. But like any other thing, this too needs a lot of practice. Get your hands dirty and this concept will help you solve great challenges with minimal efforts.


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *