paint-brush
Understanding the Power of Proxy in JavaScriptby@aleksandrguzenko
6,221 reads
6,221 reads

Understanding the Power of Proxy in JavaScript

by Aleksandr GuzenkoMarch 29th, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

JavaScript's Proxy is a capability that allows the creation of objects capable of modifying and customizing the basic operations performed on other objects. A Proxy is an object that envelops another object and intercepts basic operations on it, such as accessing, assigning, and deleting. This article delves into the capabilities of Proxy in JavaScript, covering its syntax, attributes, typical applications, strengths, limitations, and recommended approaches.
featured image - Understanding the Power of Proxy in JavaScript
Aleksandr Guzenko HackerNoon profile picture

Over the years, JavaScript has grown into a mighty and adaptable programming language, constantly evolving to cater to the changing needs of developers.


One of its relatively recent advancements is the Proxy object, which empowers programmers to create potent and flexible objects capable of intercepting and modifying key operations on other objects.


This article delves into the capabilities of Proxy in JavaScript, covering its syntax, attributes, typical applications, strengths, limitations, illustrative instances, and recommended approaches.


What is Proxy?

A Proxy is an object that envelops another object and intercepts basic operations on it, such as accessing, assigning, and deleting properties. Proxy is a crucial aspect of JavaScript that empowers developers to write more versatile and robust code.


The goal of this article is to deliver a comprehensive comprehension of Proxy in JavaScript, encompassing its syntax, characteristics, benefits, drawbacks, illustrations, and recommended techniques.

Syntax and Properties of Proxy

JavaScript's Proxy is a capability that allows the creation of objects capable of modifying and customizing the basic operations performed on other objects.


Creating a Proxy Object

To establish a Proxy object, two components are necessary: a target object and a handler object. The target object is the one on which operations are to be intercepted, while the handler object is responsible for holding the traps, or methods, used to catch these operations.


Here's an example demonstrating how to create a basic Proxy object:

const target = {
  name: 'John',
  age: 25,
};

const handler = {
  get: function(target, prop) {
    console.log(`Getting property ${prop}`);
    return target[prop];
  },
};

const proxy = new Proxy(target, handler);

console.log(proxy.name);
// Getting property name
// John


In this example, we generate a target object that has two characteristics: name and age. We also generate a handler object that has a get trap to capture any efforts to read a property on the target object. After that, we produce a Proxy object by providing the target and handler objects to the Proxy constructor. Lastly, we retrieve the name property of the Proxy object, which invokes the get trap and outputs a message to the console.


Traps and their behavior



Traps are methods that intercept operations on the target object. There are several traps that you can use with a Proxy object, including get, set, has, deleteProperty, and more.


Here's a brief overview of some of the most commonly used traps:


  1. get: This trap intercepts attempts to read a property on the target object. It takes two arguments: the target object and the property being accessed. The trap returns the value of the property.


  2. set: This trap captures any effort to establish a property on the target object. It requires three parameters: the target object itself, the property that is being established, and the updated value of that property. The mechanism has the capability to alter the value being established, or it can generate an error to prohibit the value from being established.


  3. has: This trap intercepts attempts to check if a property exists on the target object. It takes two arguments: the target object and the property being checked. The trap returns a boolean value indicating whether or not the property exists.


  4. deleteProperty: This trap intercepts attempts to delete a property from the target object. It takes two arguments: the target object and the property being deleted. The trap can delete the property or throw an error to prevent the property from being deleted.


Revocable Proxies

Proxy objects possess a fascinating characteristic that allows them to be invalidated, resulting in their traps no longer intercepting operations on the target object. To construct a Proxy object that can be invalidated, utilize the Proxy.revocable() function.


Here is an example:

const target = {
  name: 'John',
  age: 25,
};

const handler = {
  get: function(target, prop) {
    console.log(`Getting property ${prop}`);
    return target[prop];
  },
};

const {proxy, revoke} = Proxy.revocable(target, handler);

console.log(proxy.name);
// Getting property name
// John

revoke();

console.log(proxy.name);
// Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked


In this example, we create a revocable Proxy object using the Proxy.revocable() method. We then access the name property of the Proxy object, which triggers the get trap and logs a message to the console. We then revoke the Proxy object using the revoke() method, which means that any further attempts to access properties on the Proxy object will fail.


Inheritance with Proxy

Another interesting feature of Proxy objects is that they can be used to implement inheritance patterns in JavaScript. By using a Proxy object as the prototype of another object, you can intercept property lookups and customize the behavior of the prototype chain.


Here's an example:

const parent = {
  name: 'John',
};

const handler = {
  get: function(target, prop) {
    console.log(`Getting property ${prop}`);
    if (!(prop in target)) {
      return Reflect.get(parent, prop);
    }
    return target[prop];
  },
};

const child = new Proxy({}, handler);

console.log(child.name);
// Getting property name
// John

child.name = 'Bob';

console.log(child.name);
// Getting property name
// Bob

console.log(parent.name); // John


In this example, a parent object is defined and has a name attribute. Then, we create a handler object with a get trap that prevents any read requests for the child object's properties. The trap uses the Reflect.get() method to fall back to the parent object if the property is absent from the child object.


Then, using a Proxy object as the prototype and the handler object as the handler, we build a child object. Finally, we gain access to and alter the child object's name property, which sets off get and set traps and logs messages to the console.

Real-life examples using Proxy

Caching

One use case for Proxy is to cache expensive function calls. In this example, we create a Proxy object that caches the result of a function call based on its arguments.

function calculateCost(price, taxRate) {
  console.log('Calculating cost...');
  return price * (1 + taxRate);
}

const cache = new Map();

const proxy = new Proxy(calculateCost, {
  apply(target, thisArg, args) {
    const key = args.join('-');
    if (cache.has(key)) {
      console.log('Returning cached result...');
      return cache.get(key);
    } else {
      const result = Reflect.apply(target, thisArg, args);
      cache.set(key, result);
      return result;
    }
  },
});

console.log(proxy(10, 0.2)); // Calculating cost...  12
console.log(proxy(10, 0.2)); // Returning cached result... 12
console.log(proxy(20, 0.2)); // Calculating cost...  24
console.log(proxy(20, 0.3)); // Calculating cost...  26
console.log(proxy(20, 0.3)); // Returning cached result...  26

In this example, we define a function called calculateCost that takes a price and a tax rate and returns the cost with tax. We then create a cache object using the Map class.


Next, we create a Proxy object called proxy that intercepts function calls using the apply trap. The apply trap is called whenever the function is called, and it receives the function arguments as an array. We use the arguments to generate a cache key and check if the result is already in the cache. If it is, we return the cached result. Otherwise, we calculate the result and store it in the cache.


Finally, we invoke the proxy function using different arguments and observe that the outcome is stored in the cache for subsequent calls with identical arguments.

Validation

Another use case for Proxy is to validate object properties. In this example, we create a Proxy object that validates the length of a string property.

const user = {
  name: 'John',
  password: 'secret',
};

const proxy = new Proxy(user, {
  set(target, prop, value) {
    if (prop === 'password' && value.length < 8) {
      throw new Error('Password must be at least 8 characters long');
    }
    target[prop] = value;
    return true;
  },
});

console.log(proxy.name); // John
console.log(proxy.password); // secret

proxy.password = '12345678';
console.log(proxy.password); // 12345678

proxy.password = '123'; // Error


In this example, we define an object called user with a name and a password property. We then create a Proxy object called proxy that intercepts property assignments using the set trap. The set trap is called whenever a property is assigned, and it receives the property name, the new value, and the target object.


We use the set trap to check if the property being assigned is the password property and if the value is less than 8 characters long. If it is, we throw an error. Otherwise, we set the property value on the target object.


We utilize the proxy object to assign various values to the password property and note that any values under 8 characters in length trigger an error.

Logging

Another common use case for Proxy is to log object property accesses and assignments. In this example, we create a Proxy object that logs property accesses and assignments.

const user = {
  name: 'John',
  email: '[email protected]',
};

const proxy = new Proxy(user, {
  get(target, prop) {
    console.log(`Getting ${prop} property`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Setting ${prop} property to ${value}`);
    target[prop] = value;
    return true;
  },
});

console.log(proxy.name); // Getting name property -> John
proxy.email = '[email protected]'; // Setting email property to [email protected]
console.log(proxy.email); // Getting email property -> [email protected]

In this example, we define an object called user with a name and an email property. We then create a Proxy object called proxy that intercepts property accesses and assignments using the get and set traps.


The get trap is called whenever a property is accessed, and it receives the property name and the target object. In this example, we log a message to the console indicating that the property is being accessed, and then we return the property value from the target object.


The set trap is called whenever a property is assigned, and it receives the property name, the new value, and the target object. In this example, we log a message to the console indicating that the property is being assigned, and then we set the property value on the target object.


Finally, we access and assign various properties using the proxy object and observe that messages are logged to the console.

Advantages and Limitations of Proxy

Advantages of Proxy

  1. Customizable behavior: With proxy objects, you can intercept and customize basic operations on other objects, allowing you to create advanced features such as access control, caching, and logging.


  2. Inheritance: Proxy objects offer the ability to implement inheritance patterns in JavaScript, which can lead to more versatile and scalable code.


  3. Revocable: Proxy objects can be disabled or revoked after they are created, making them useful for limiting the scope of the proxy object or for security reasons.


Limitations of Proxy

  1. Despite the fact that the Proxy has been with us for a long time, not all versions of browsers can support this functionality.


  2. Moreover, the use of Proxies can negatively affect the performance of your application, especially if you use them too often.


  3. It is important to understand the meaning of using a proxy. It should not be trusted for application-critical moments, such as important validation of user input.

Best Practices for Using Proxy

Be aware of the restrictions: Before implementing a proxy in your code, be aware of the restrictions it imposes and how they may affect the speed and security of your application.

Proxy objects should only be used when absolutely essential because they can affect your code's performance.


Test carefully: When utilizing Proxy objects, be sure to test carefully and be alert to any potentially unexpected behavior.


Adhere to norms: To make your code simple to read and maintain, adhere to accepted conventions and best practices while implementing Proxy objects.

Conclusion

The article delves into advanced features of Proxy, such as inheritance patterns and the ability to create revocable Proxy objects.


Irrespective of your experience level as a developer, comprehending Proxy in JavaScript is fundamental to elevating your code to a higher level.


Due to its adaptability and potency, Proxy constitutes a critical instrument for any JavaScript developer aspiring to construct intricate applications with ease.