JavaScript Proxy: A Comprehensive Tutorial

In JavaScript, the Proxy object enables you to intercept and customize the behavior of fundamental operations on objects, such as property lookup, assignment, enumeration, function invocation, and more.

With Proxy, you can control the behavior of objects in ways that weren't possible before, making it a powerful tool for meta-programming, validation, logging, and more.

In this tutorial, we will explore how to use JavaScript Proxy, with several practical examples demonstrating its potential uses.

1. What is a Proxy?

A Proxy is an object that wraps another object (the “target”) and intercepts operations performed on that target object. You define handlers (functions) that intercept these operations, like getting or setting properties.

The syntax for creating a Proxy is:

const proxy = new Proxy(target, handler);

target: The object that the proxy will wrap.
handler: An object containing traps (functions) that define how to intercept operations on the target.

2. Basic Example of Proxy

Let's start with a simple example of using a Proxy to log every time a property is accessed or set on an object.

Example 1: Logging Property Access and Assignment

const person = {
  name: 'Alice',
  age: 25
};

// Define a handler with get and set traps
const handler = {
  get(target, prop) {
    console.log(`Getting property "${prop}"`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Setting property "${prop}" to "${value}"`);
    target[prop] = value;
    return true;
  }
};

// Create a proxy that wraps the person object
const personProxy = new Proxy(person, handler);

// Access and set properties
console.log(personProxy.name);    // Output: Getting property "name" / "Alice"
personProxy.age = 30;             // Output: Setting property "age" to "30"
console.log(personProxy.age);     // Output: Getting property "age" / "30"

Explanation:

The get() trap is used to intercept property access, and it logs the property name being accessed.
The set() trap intercepts assignments and logs when a property is updated, before assigning the new value to the target object.

3. Validation with Proxies

Proxies can be used to add validation logic when modifying object properties, ensuring that certain conditions are met before an update occurs.

Example 2: Validating Property Assignments

const user = {
  age: 20
};

const handler = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (typeof value !== 'number' || value < 0) {
        throw new Error('Age must be a non-negative number');
      }
    }
    target[prop] = value;
    return true;
  }
};

const userProxy = new Proxy(user, handler);

userProxy.age = 25;  // Works fine
console.log(userProxy.age); // Output: 25

userProxy.age = -5;  // Throws Error: Age must be a non-negative number

Explanation:

The set() trap is used to validate the value of the age property. It ensures that the age is a non-negative number.
If the validation fails, an error is thrown, preventing the property from being updated.

4. Automatic Default Values with Proxies

You can use proxies to return default values for properties that are not set or undefined, which is useful for objects where you want every property to have a default value.

Example 3: Returning Default Values

const defaults = {
  name: 'Anonymous',
  age: 0
};

const handler = {
  get(target, prop) {
    return prop in target ? target[prop] : `Default ${prop}`;
  }
};

const defaultsProxy = new Proxy(defaults, handler);

console.log(defaultsProxy.name);  // Output: "Anonymous"
console.log(defaultsProxy.age);   // Output: 0
console.log(defaultsProxy.gender); // Output: "Default gender"

Explanation:

The get() trap checks whether a property exists on the target object. If it doesn’t, a default value (“Default “) is returned.
In this case, accessing a non-existent property (gender) returns a default message.

5. Read-Only Objects Using Proxies

Proxies can make an object read-only by preventing any modifications to its properties. This can be useful for configuration objects that should not be changed after initialization.

Example 4: Creating a Read-Only Object

const config = {
  apiEndpoint: 'https://api.example.com',
  timeout: 5000
};

const handler = {
  set() {
    console.log('Cannot modify read-only object');
    return false;  // Prevent any modifications
  }
};

const configProxy = new Proxy(config, handler);

console.log(configProxy.apiEndpoint); // Output: "https://api.example.com"

configProxy.apiEndpoint = 'https://new-api.example.com';  // Output: "Cannot modify read-only object"
console.log(configProxy.apiEndpoint); // Output: "https://api.example.com"

Explanation:

The set() trap intercepts any attempts to modify the object and prevents the change by returning false.
This effectively makes the configProxy a read-only object.

6. Intercepting Function Calls

Proxies can also be used to intercept and control function invocations. You can modify the way functions are called by using the apply() trap.

Example 5: Modifying Function Behavior

function sum(a, b) {
  return a + b;
}

const handler = {
  apply(target, thisArg, argumentsList) {
    console.log(`Calling sum with arguments: ${argumentsList}`);
    return target(...argumentsList) * 2;  // Double the result
  }
};

const sumProxy = new Proxy(sum, handler);

console.log(sumProxy(5, 10));  // Output: "Calling sum with arguments: 5,10" / 30

Explanation:

The apply() trap intercepts calls to the sum() function.
It logs the arguments passed to the function and modifies the return value by doubling it.

7. Handling Property Deletion

You can use the deleteProperty() trap to intercept attempts to delete properties from an object. This is useful if you want to prevent or log property deletions.

Example 6: Preventing Property Deletion

const car = {
  make: 'Toyota',
  model: 'Corolla'
};

const handler = {
  deleteProperty(target, prop) {
    console.log(`Attempt to delete property "${prop}" was blocked.`);
    return false;  // Prevent property deletion
  }
};

const carProxy = new Proxy(car, handler);

delete carProxy.model;  // Output: "Attempt to delete property 'model' was blocked."
console.log(carProxy.model); // Output: "Corolla"

Explanation:

The deleteProperty() trap intercepts attempts to delete properties and blocks the deletion by returning false.

8. Dynamic Property Generation

You can use a Proxy to dynamically generate properties that don't exist in the target object. This is useful for creating objects that can respond to arbitrary keys or keys that depend on external logic.

Example 7: Dynamic Property Generation

const dynamicObject = {
  existingProperty: 'I am here'
};

const handler = {
  get(target, prop) {
    if (prop in target) {
      return target[prop];
    } else {
      return `Property "${prop}" generated dynamically`;
    }
  }
};

const dynamicProxy = new Proxy(dynamicObject, handler);

console.log(dynamicProxy.existingProperty);  // Output: "I am here"
console.log(dynamicProxy.newProperty);       // Output: 'Property "newProperty" generated dynamically'

Explanation:

If a requested property doesn’t exist in the object, the get() trap dynamically generates a message for that property.

Conclusion

JavaScript Proxy provides a powerful mechanism to intercept and customize the behavior of objects, making it a valuable tool for meta-programming. With Proxy, you can modify object behavior, validate data, log operations, create read-only objects, and more.

Key Takeaways:

Basic Usage: Use Proxy to intercept fundamental object operations like property access, assignment, deletion, and more.
Validation: Validate and restrict changes to object properties using the set() trap.
Read-Only Objects: Make objects read-only by intercepting and blocking write operations.
Function Invocation: Use the apply() trap to modify the behavior of function calls.
Dynamic Properties: Generate properties dynamically using the get() trap.

Experiment with the examples in this tutorial to understand how you can leverage proxies to enhance your JavaScript objects and functions!

Related posts

JavaScript Template Literals Tutorial with Examples

JavaScript Reflect: A Comprehensive Tutorial

JavaScript async/await: A Comprehensive Tutorial