Logging Access to Object Properties in JavaScript Using Proxy

This Question was asked in ServiceNow interview, it can be solved using the Proxy Design Pattern. When working with JavaScript objects, there are times when you might want to track when a property is accessed or modified. This can be useful for debugging, performance monitoring, or simply understanding how your object is being used in a large application.

In this article, we’ll explore how you can log property access using the Proxy object in JavaScript, which provides a powerful way to intercept and customize fundamental operations on objects.

πŸ’‘ What is a Proxy in JavaScript?

A Proxy is an object that wraps another object and allows you to intercept operations like getting, setting, and deleting properties.

Here’s how a Proxy works:

  • You create a target object (the original object).
  • You define a handler that specifies what to do when certain operations are performed on the object.

The most common traps used are:

  • get: Triggers when a property is accessed.
  • set: Triggers when a property is assigned a value.

πŸš€ Logging Property Access with Proxy

Let’s create an object and log a message every time a property is accessed.

Example:

const user = {
  name: 'John',
  age: 30,
  city: 'New York'
};

const handler = {
  get(target, prop) {
    console.log(`Property "${prop}" was accessed.`);
    return target[prop]; // Return the actual property value
  }
};

const proxiedUser = new Proxy(user, handler);

// Test property access
console.log(proxiedUser.name);  // Logs: Property "name" was accessed. Output: John
console.log(proxiedUser.age);   // Logs: Property "age" was accessed. Output: 30

πŸ” Explanation:

  1. user: This is the original object.
  2. handler: The object defining the custom behavior for property access.
  3. Proxy: We create a proxy (proxiedUser) around the user object, passing in the target and the handler.
  4. get trap: The get method logs a message each time a property is accessed and returns the actual value of the property (target[prop]).

πŸ› οΈ Logging Both Access and Modifications

You can extend this behavior to log not just access but also when a property is modified.

Example:

const handler = {
  get(target, prop) {
    console.log(`Property "${prop}" was accessed.`);
    return target[prop];
  },
  set(target, prop, value) {
    console.log(`Property "${prop}" was set to "${value}".`);
    target[prop] = value; // Set the new value
    return true;
  }
};

const proxiedUser = new Proxy(user, handler);

// Test property modification
proxiedUser.age = 35; // Logs: Property "age" was set to "35".
console.log(proxiedUser.age);  // Logs: Property "age" was accessed. Output: 35

🌟 Benefits of Using Proxy for Property Logging

  • Debugging & Monitoring: Helps track how your objects are used.
  • Security: Enforces rules for accessing or modifying certain properties.
  • Reactive Systems: Can be used to build reactive systems (like Vue’s reactivity system).

⚠️ Things to Keep in Mind

  • Performance: Proxies add a slight performance overhead, so use them wisely in performance-critical code.
  • Non-Enumerable Properties: Proxies may not catch certain properties that are non-enumerable.
  • Compatibility: Proxies are not supported in Internet Explorer.

πŸ”— Use Cases

  • Logging access and modification for debugging
  • Data validation or enforcing rules
  • Building reactive data layers (similar to frameworks like Vue.js)

Example question mentioned by a LinkedIn user:

if someone tries to set the age to less than 18, then it should throw an error.

const user = {
  name: 'John',
  age: 30,
  city: 'New York'
};

const handler = {
  get(target, prop) {
    console.log(`Property "${prop}" was accessed.`);
    return target[prop];
  },
  set(target, prop, value) {
    if (prop === 'age' && value < 18) {
      throw new Error(`Invalid age: ${value}. Age must be 18 or older.`);
    }
    console.log(`Property "${prop}" was set to "${value}".`);
    target[prop] = value; // Set the new value
    return true;
  }
};

const proxiedUser = new Proxy(user, handler);

// Test property modification
try {
  proxiedUser.age = 16;  // Throws: Error: Invalid age: 16. Age must be 18 or older.
} catch (error) {
  console.error(error.message);
}

proxiedUser.age = 25;  // Logs: Property "age" was set to "25".
console.log(proxiedUser.age);  // Logs: Property "age" was accessed. Output: 25

Why Use a Proxy for Validation?

  • Centralized validation logic for all property changes.
  • Easily extendable to add more rules for different properties.
  • Improves code readability and maintainability.