A JavaScript proxy object lets you intercept and customize the behavior of another object, without modifying the original.

Using proxy objects, you can validate data, provide extra functionality, and control access to properties and functions.

Find out all about the uses of proxy objects and how you can create them in JavaScript.

Creating a Proxy Object

In JavaScript, you can create proxy objects using the Proxy constructor. This constructor takes two arguments: a target object to wrap the proxy around and a handler object whose properties define the proxy’s behavior when you carry out operations.

It takes these arguments and creates an object you can use in place of the target object. This created object can redefine core operations such as getting, setting, and defining properties. You can also use these proxy objects to log property accesses and validate, format, or sanitize inputs.

For example:

        const originalObject = {
  foo: "bar"
}

const handler = {
  get: function(target, property) {
    return target[property];
  },
  set: function(target, property, value) {
    target[property] = value;
  }
};

const proxy = new Proxy(originalObject, handler)

This code creates a target object, originalObject, with a single property, foo, and a handler object, handler. The handler object contains two properties, get and set. These properties are known as traps.

A proxy object trap is a function called whenever you perform a specified action on a proxy object. Traps allow you to intercept and customize the behavior of the proxy object. Accessing a property from the proxy object calls the get trap, and modifying or manipulating a property from the proxy object calls the set trap.

Finally, the code creates a proxy object with the Proxy constructor. It passes originalObject and handler as the target object and handler, respectively.

Using Proxy Objects

Proxy objects have several uses in JavaScript, some of which are as follows.

Adding Functionality to an Object

You can use a proxy object to wrap an existing object and add new functionality, such as logging or error handling, without modifying the original object.

To add new functionality, you’ll need to use the Proxy constructor and define one or more traps for the actions you want to intercept.

For example:

        const userObject = {
  firstName: "Kennedy",
  lastName: "Martins",
  age: 20,
};

const handler = {
  get: function (target, property) {
    console.log(`Getting property "${property}"`);
    return target[property];
  },
  set: function (target, property, value) {
    console.log(`Setting property "${property}" to value "${value}"`);
    target[property] = value;
  },
};

const proxy = new Proxy(userObject, handler);

console.log(proxy.firstName); // Getting property "firstName" Kennedy
console.log(proxy.lastName); // Getting property "lastName" Martins
proxy.age = 23; // Setting property "age" to value "23"

This code block adds functionality via the proxy traps, get and set. Now, when you try to access or modify a property of the userObject, the proxy object will first log your operation to the console before accessing or modifying the property.

Validating Data Before Setting It on an Object

You can use proxy objects to validate data and ensure it meets certain criteria before setting it on an object. You can do so by defining the validation logic in a set trap in the handler object.

For example:

        const userObject = {
  firstName: "Kennedy",
  lastName: "Martins",
  age: 20,
};

const handler = {
  get: function (target, property) {
    console.log(`Getting property "${property}"`);
    return target[property];
  },
  set: function (target, property, value) {
    if (
      property === "age" &&
      typeof value == "number" &&
      value > 0 &&
      value < 120
    ) {
      console.log(`Setting property "${property}" to value "${value}"`);
      target[property] = value;
    } else {
      throw new Error("Invalid parameter. Please review and correct.");
    }
  },
};

const proxy = new Proxy(userObject, handler);
proxy.age = 21;

This code block adds validation rules to the set trap. You can assign any value to the age property on a userObject instance. But, with the added validation rules, you can only assign a new value to the age property if it’s a number, greater than 0, and less than 120. Any value you try to set on the age property that does not meet the required criteria will trigger an error and print an error message.

Controlling Access to Object Properties

You can use proxy objects to hide certain properties of an object. Do so by defining restriction logic in get traps for the properties you want to control access to.

For example:

        const userObject = {
  firstName: "Kennedy",
  lastName: "Martins",
  age: 20,
  phone: 1234567890,
  email: "foo@bar.com",
};

const handler = {
  get: function (target, property) {
    if (property === "phone" || property === "email") {
      throw new Error("Access to info denied");
    } else {
      console.log(`Getting property "${property}"`);
      return target[property];
    }
  },
  set: function (target, property, value) {
    console.log(`Setting property "${property}" to value "${value}"`);
    target[property] = value;
  },
};

const proxy = new Proxy(userObject, handler);

console.log(proxy.firstName); // Getting property "firstName" Kennedy
console.log(proxy.email); // Throws error

The code block above adds certain restrictions to the get trap. Initially, you can access all available properties on userObject. The added rules prevent access to sensitive information such as the user’s email or phone. Trying to access either of these properties will trigger an error.

Other Proxy Traps

The get and set traps are the most common and useful, but there are 11 other JavaScript proxy traps. They are:

  • apply: The apply trap runs when you call a function on the proxy object.
  • construct: The construct trap runs when you use the new operator to create an object from the proxy object.
  • deleteProperty: The deleteProperty trap runs when you use the delete operator to remove a property from the proxy object.
  • has - The has trap runs when you use the in operator to check if a property exists on the proxy object.
  • ownKeys - The ownKeys trap runs when you call either the Object.getOwnPropertyNames or Object.getOwnPropertySymbols function on the proxy object.
  • getOwnPropertyDescriptor - The getOwnPropertyDescriptor trap runs when you call the Object.getOwnPropertyDescriptor function on the proxy object.
  • defineProperty - The defineProperty trap runs when you call the Object.defineProperty function on the proxy object.
  • preventExtensions - The preventExtensions trap runs when you call the Object.preventExtensions function on the proxy object.
  • isExtensible - The isExtensible trap runs when you call the Object.isExtensible function on the proxy object.
  • getPrototypeOf - The getPrototypeOf trap runs when you call the Object.getPrototypeOf function on the proxy object.
  • setPrototypeOf - The setPrototypeOf trap runs when you call the Object.setPrototypeOf function on the proxy object.

Like the set and get traps, you can use these traps can to add new layers of functionality, validation, and control to your object without modifying the original.

The Cons of Proxy Objects

Proxy objects can be a powerful tool for adding custom functionality or validation to an object. But they also have some potential drawbacks. One such drawback is difficulty debugging, as it can be hard to see what’s happening behind the scenes.

Proxy objects can also be difficult to use, especially if you are unfamiliar with them. You should carefully consider these drawbacks before using proxy objects in your code.