JavaScript Proxies


In JavaScript, a Proxy is a powerful and flexible mechanism that allows you to define custom behavior for fundamental operations on objects. These operations can include reading, writing, updating, and deleting properties, as well as handling function invocations, among others.

JavaScript Proxies were introduced in ECMAScript 6 (ES6), offering a way to interact with objects in a more controlled manner. This allows developers to intercept and redefine how operations on objects are performed.


What is a JavaScript Proxy?

A Proxy is an object that wraps another object or function and intercepts its interactions (like method calls, property lookups, and assignments). This interception allows you to modify or extend the behavior of the object without altering the original implementation.

Basic Syntax of a Proxy

let handler = {
  get: function(target, prop, receiver) {
    console.log(`Getting property ${prop}`);
    return prop in target ? target[prop] : `Property ${prop} not found`;
  },
  set: function(target, prop, value) {
    console.log(`Setting property ${prop} with value ${value}`);
    target[prop] = value;
    return true;
  }
};

let proxy = new Proxy(targetObject, handler);
  • target: The original object that the Proxy is wrapping.
  • handler: An object that defines the traps (custom behaviors) for operations like getting or setting properties.

Key Concepts in Proxies

A Proxy allows you to intercept various operations. The most common ones are:

  1. get: Intercepts the reading of a property.
  2. set: Intercepts setting a property’s value.
  3. has: Intercepts checking the existence of a property.
  4. deleteProperty: Intercepts the deletion of a property.
  5. apply: Intercepts function calls.
  6. construct: Intercepts object construction via the new keyword.

Example 1: Basic Proxy Usage

In this simple example, we will create a Proxy to log whenever a property is accessed or modified in an object.

Sample Code: Basic Proxy Example

let person = {
  name: 'Alice',
  age: 30
};

let handler = {
  get: function(target, prop) {
    console.log(`Getting ${prop}`);
    return prop in target ? target[prop] : `Property ${prop} not found`;
  },
  set: function(target, prop, value) {
    console.log(`Setting ${prop} to ${value}`);
    target[prop] = value;
    return true; // Indicate that the set was successful
  }
};

let proxyPerson = new Proxy(person, handler);

console.log(proxyPerson.name);  // Output: Getting name, Alice
proxyPerson.age = 31;           // Output: Setting age to 31
console.log(proxyPerson.age);   // Output: Getting age, 31

Explanation:

  • When proxyPerson.name is accessed, the get trap logs the access and retrieves the value of name.
  • When proxyPerson.age = 31 is executed, the set trap logs the change and updates the age property.

Example 2: Validating Property Changes with set Trap

In this example, we will use a Proxy to enforce rules when setting properties on an object. For instance, we can prevent setting a negative value for age.

Sample Code: Proxy with Validation

let person = {
  name: 'John',
  age: 25
};

let handler = {
  set: function(target, prop, value) {
    if (prop === 'age' && value < 0) {
      console.log('Age cannot be negative!');
      return false; // Prevent the change
    }
    target[prop] = value;
    return true; // Indicate success
  }
};

let proxyPerson = new Proxy(person, handler);

proxyPerson.age = -5;  // Output: Age cannot be negative!
console.log(proxyPerson.age);  // Output: 25
proxyPerson.age = 30;   // Age is successfully updated
console.log(proxyPerson.age);  // Output: 30

Explanation:

  • The set trap checks if the property being modified is age and if the new value is negative. If so, it prevents the update and logs an error.

Example 3: Using Proxy with Function Objects

Proxies are not limited to objects; they can also be used with functions to intercept function calls. In this example, we will use a Proxy to log every time a function is called.

Sample Code: Proxy for Function Calls

function greet(name) {
  return `Hello, ${name}!`;
}

let handler = {
  apply: function(target, thisArg, argumentsList) {
    console.log(`Calling function with arguments: ${argumentsList}`);
    return target(...argumentsList); // Call the original function
  }
};

let proxyGreet = new Proxy(greet, handler);

console.log(proxyGreet('Alice'));  // Output: Calling function with arguments: Alice, Hello, Alice!

Explanation:

  • The apply trap intercepts the function call. We log the arguments and then call the original greet function with those arguments.

Example 4: Using Proxy for Property Existence Checking

Another useful feature of proxies is the ability to intercept the in operator, which checks if a property exists in an object.

Sample Code: Proxy with has Trap

let person = {
  name: 'Bob',
  age: 40
};

let handler = {
  has: function(target, prop) {
    if (prop === 'age') {
      console.log('Age property is being checked');
    }
    return prop in target;
  }
};

let proxyPerson = new Proxy(person, handler);

console.log('name' in proxyPerson);  // Output: true
console.log('age' in proxyPerson);   // Output: Age property is being checked, true
console.log('height' in proxyPerson);  // Output: false

Explanation:

  • The has trap is called whenever the in operator is used to check the existence of a property in the proxy object.