31.5 C
Pakistan
Saturday, July 27, 2024

An Examination of JavaScript Proxies with Code Samples

JavaScript proxies are a strong and flexible feature that let programmers modify and intercept how functions, arrays, and objects behave. This can be very helpful in many different situations, such as data validation, error correction, performance enhancement, and even security.

This post will explain the idea of proxies in JavaScript, explain how they operate, and go over some real-world applications for them.

Proxy servers: What are they?

An object known as a proxy encapsulates another object and grants you the ability to intercept and modify its basic functions, including reading and writing properties, invoking methods, and managing errors.

In ECMAScript 6 (ES6), proxies were included as a component of the  Reflect API, 

which offers a number of low-level techniques for manipulating objects and their attributes.

Making a new proxy object has an easy-to-understand syntax. This is how it appears:

const proxy = new Proxy(target, handler);

The object we wish to proxy is called the target, and the object with one or more methods that specify the behavior of the proxy is called the handler.

With the help of the handler object’s numerous predefined methods—including get, set, apply, construct, and has—we can intercept and modify a range of operations performed on the target object.

Assume we have an object named user with a few methods and properties.

const user = {
  name: "John",
  age: 30,
  sayHello() {
    console.log(
      `Hello, my name is ${this.name} and I am ${this.age} years old.`
    );
  },
};

By defining a handler object that has a get method, we can modify the behavior of this object and create a proxy for it. Here’s how it works:

const handler = {
  get(target, prop) {
    if (prop === "age") {
      return target[prop] + 10; // add 10 to the actual age
    } else {
      return target[prop]; // return the actual property value
    }
  },
}; 

const proxy = new Proxy(user, handler);

console.log(proxy.name); // "John"
console.log(proxy.age); // 40
proxy.sayHello(); // "Hello, my name is John and I am 30 years old."

We’ve defined a get method in the handler object and made a proxy for the user object. Any attempt to read a property from the proxy is intercepted by this method, which then uses custom logic based on the property name.

In this instance, we add 10 to the property’s actual value before returning it if the property is age.

Realistic Applications of Proxies

Proxies come in handy in a number of situations where you need to alter an object’s behavior, verify data input, deal with errors, or enhance performance. Let’s examine a few real-world instances.

Validating Data and Managing Errors

Prior to processing or storing user-inputted data, proxies can be used to verify and clean it.

For instance, you can use a proxy to stop any attempt to set invalid values if you have a form object.

const form = {
  name: "",
  email: "",
  password: "",
};

const validator = {
  set(target, prop, value) {
    if (prop === "email" && !isValidEmail(value)) {
      throw new Error("Invalid email address");
    }
    if (prop === "password" && !isValidPassword(value)) {
      throw new Error("Password must contain at least 8 characters");
    }
    target[prop] = value;
    return true;
  },
};

const formProxy = new Proxy(form, validator);

formProxy.name = "John";
formProxy.email = "john.doe@example.com"; // throws an Error: "Invalid email address"
formProxy.password = "1234"; // throws an Error: "Password must contain at least 8 characters"

We defined a set method in the validator object and made a proxy for a form object. Any attempt to set a property value on the form proxy is intercepted by this method, which then verifies if it satisfies certain validation requirements. It sets the target object’s property value and returns true if the value is valid. If not, an error with a personalized message is thrown.

Enhancement of Performance

Additionally, proxies can be used to minimize pointless computations and enhance the performance of intricate data structures. As an illustration, you can make a proxy for a sizable array and stop any attempt to access its components. You can cache the computed values and return them straight from the proxy, saving you the trouble of repeatedly iterating over the entire array.

Let’s examine this.

const data = Array(1000000)
  .fill(0)
  .map(() => Math.random());

const cachedData = {
  cache: new Map(),
  get(target, prop) {
    if (!this.cache.has(prop)) {
      this.cache.set(prop, target[prop]);
    }
    return this.cache.get(prop);
  },
};

const dataProxy = new Proxy(data, cachedData);

console.log(dataProxy[0]); // 0.23541347507630996
console.log(dataProxy[0]); // 0.23541347507630996 (cached)
console.log(dataProxy[500000]); // 0.18901203147133367
console.log(dataProxy[500000]); // 0.18901203147133367 (cached)

We defined a get method in the cachedData object and built a proxy for the data, which is a large array.

This technique checks to see if a data proxy element has already been computed and cached and intercepts any attempt to access it. In that case, the cached value is returned. If not, the value is computed, cached, and then returned.

Safety

Proxies can also be used to improve code security and stop unwanted access to private information.

One way to prevent attempts to read or alter a private object’s properties is to establish a proxy for it.

This can assist you in enforcing the least privilege principle and limiting access to specific code sections to only the information that is required.

const privateData = {
  secret: "this is a secret message",
};

const security = {
  get(target, prop) {
    if (prop === "secret") {
      throw new Error("Access denied");
    } else {
      return target[prop];
    }
  },
  set(target, prop, value) {
    if (prop === "secret") {
      throw new Error("Access denied");
    } else {
      target[prop] = value;
      return true;
    }
  },
};

const privateProxy = new Proxy(privateData, security);

console.log(privateProxy.secret); // throws an Error: "Access denied"
privateProxy.secret = "new secret"; // throws an Error: "Access denied"
privateProxy.name = "John"; // sets "name" property on the private object
console.log(privateProxy.name); // "John"

In this example, we defined a get and set method in the security object and made a proxy for a private object named privateData. These techniques intercept any attempt to read or change a private proxy property and determine whether it is permissible in accordance with security guidelines.

Troubleshooting

Additionally, you can use proxies to trace changes made to objects and debug your code. You could, for instance, make a proxy for an object and stop anyone trying to change its attributes. This can assist you in identifying any unexpected changes and comprehending the behavior of your code:

const debugData = {
  name: "John Doe",
  age: 30,
};

const debuggerProxy = new Proxy(debugData, {
  set(target, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
    return true;
  },
});

debuggerProxy.age = 31; // logs "Setting age to 31"

We defined a set method in the proxy object that we made for the debugData object.

This method logs the property name and value to the console and stops any attempt to set a proxy object property value. After that, it returns true and modifies the target object’s property value.

Conclusion

One useful tool for improving JavaScript object behavior is a proxy. Without altering the source code of the object, they let you intercept and modify its behavior.

Numerous features, including validation, logging, caching, interception, and security, can be implemented with proxies.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles