🟨 JavaScript Q66 / 121

Explain Proxy object.

AI-Powered Answer ✓ Answered

The JavaScript `Proxy` object allows you to create an object that can be used in place of another object (the target) but can intercede operations performed on the target. It enables you to define custom behavior for fundamental operations like property lookup, assignment, enumeration, function invocation, and more.

What is a Proxy?

A Proxy object wraps another object, called the 'target'. It acts as an intermediary, allowing you to intercept and customize operations performed on the target object. This interception is done through a 'handler' object, which defines specific methods called 'traps'.

Key Concepts

Target Object

The object that the proxy virtualizes. It can be any object, including another proxy, a function, or even an empty object. The operations (like getting a property) that are not intercepted by the handler will be forwarded to the target object by default.

Handler Object

An object containing 'trap' methods. Each trap corresponds to a specific fundamental operation on the proxy. If a trap is not defined in the handler, the operation is forwarded to the target object.

Traps

Methods defined in the handler object that intercept fundamental operations. When an operation is performed on the proxy, if a corresponding trap exists in the handler, that trap method is executed instead of the default operation on the target. For example, the get trap intercepts property reads, and the set trap intercepts property assignments.

  • get(target, property, receiver): Intercepts property reads.
  • set(target, property, value, receiver): Intercepts property assignments.
  • apply(target, thisArg, argumentsList): Intercepts function calls.
  • construct(target, argumentsList, newTarget): Intercepts new operator calls.
  • has(target, property): Intercepts in operator.
  • deleteProperty(target, property): Intercepts delete operator.
  • ownKeys(target): Intercepts Object.keys(), Object.getOwnPropertyNames(), Object.getOwnPropertySymbols().

Syntax

javascript
const p = new Proxy(target, handler);

Example

Basic Property Access Trap

javascript
const target = {
  message1: 'hello',
  message2: 'world'
};

const handler = {
  get(target, property, receiver) {
    console.log(`Getting property '${String(property)}'`);
    return property in target ? target[property] : `Property '${String(property)}' does not exist.`;
  },
  set(target, property, value, receiver) {
    console.log(`Setting property '${String(property)}' to '${value}'`);
    if (typeof value !== 'string') {
      throw new TypeError('Value must be a string.');
    }
    target[property] = value;
    return true;
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.message1); // Output: Getting property 'message1'
                              //         hello
console.log(proxy.message3); // Output: Getting property 'message3'
                              //         Property 'message3' does not exist.

proxy.message1 = 'hi';     // Output: Setting property 'message1' to 'hi'
console.log(target.message1); // Output: hi

try {
  proxy.message4 = 123;  // Output: Setting property 'message4' to '123'
                         //         TypeError: Value must be a string.
} catch (e) {
  console.error(e.message);
}

Validation Example

javascript
const validator = {
  set(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value < 0 || value > 200) {
        throw new RangeError('The age is out of range');
      }
    }
    // The default behavior to store the value
    obj[prop] = value;
    return true;
  }
};

const person = new Proxy({}, validator);

person.age = 35;
console.log(person.age); // 35

// person.age = 'young'; // Throws TypeError: The age is not an integer
// person.age = -1;      // Throws RangeError: The age is out of range

Use Cases

  • Validation: Intercepting property assignments to validate input (as shown in the example).
  • Logging: Logging operations performed on an object (reads, writes, function calls).
  • Access Control/Authorization: Preventing access to certain properties or methods based on permissions.
  • Data Binding: Automatically updating UI components when an object's properties change.
  • Memoization: Caching results of expensive function calls.
  • Reactivity (e.g., Vue.js 3): Making objects reactive by intercepting property access and modification.
  • Revocable Proxies: Creating proxies that can be revoked, making them unusable after a certain point.
  • Debugging: Monitoring object interactions.

Limitations and Considerations

  • Performance: Proxies can introduce a slight performance overhead compared to direct object operations, though often negligible for most applications.
  • Complexity: Over-reliance on proxies for simple tasks can make code harder to understand and debug.
  • this context: When using proxies with methods that rely on this, ensure the this context is correctly handled, often by explicitly binding or using the receiver argument in traps.
  • Invariants: Several traps have 'invariants' that must be maintained (e.g., a non-configurable, non-writable property of the target must retain the same value when set via the proxy). Failing to respect these invariants will result in a TypeError.