DEV Community

Cover image for 🧠 Understanding JavaScript Proxies: Harnessing the Power of Metaprogramming
Ishraq Tanvir
Ishraq Tanvir

Posted on • Originally published at Medium

🧠 Understanding JavaScript Proxies: Harnessing the Power of Metaprogramming

Hello fellow developers! Have you ever needed more control over how objects behave in JavaScript? Today, we will delve into the powerful yet often misunderstood feature of JavaScript: Proxies. Proxies are not hard but the syntax is a bit strange at first sight! Let’s explore what they are, how they work, and some practical use cases.

What is a Proxy?

A Proxy in JavaScript is an object that wraps another object (called the target) and intercepts fundamental operations performed on it, such as property access, assignment, and function invocation. By defining custom behavior for these operations, Proxies allows developers to create highly dynamic and flexible applications.

‘In Small terms Proxy is a Wrapper over an object for advanced capability over objects’

Syntax

Creating a Proxy is straightforward. Here’s the basic syntax:

const proxy = new Proxy(target, handler);

  • target: The object that the proxy will wrap.
  • handler: An object containing traps (functions) that define the custom behavior for various operations.

Traps

Traps are the methods provided in the handler object to intercept operations. Some common traps include:

  • get: Handles property access.
  • set: Handlesproperty assignment.
  • has: Handles the in operator.
  • deleteProperty: Handles property deletion.
  • apply: Handles function calls.
  • construct: Handles the new operator.

Creating a Simple Proxy

Let’s create a simple proxy to log property access and assignments.

const target = {
       name: 'Ishraq',
       age: 15      
};

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

const proxy = new Proxy(target, handler);
console.log(proxy.name);  // Logs "Getting property name"
proxy.age = 19;           // Logs "Setting property age to 19"
console.log(proxy.age);   // Logs "Getting property age"

Enter fullscreen mode Exit fullscreen mode

Validation

Proxies can be used to enforce validation rules on objects. This is especially useful when dealing with user input or API responses. It opens a new layer of validation checking!

const user = {
      name: '',
      age: 0
};

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

const proxy = new Proxy(user, handler);
proxy.age = 25;  // Works fine
proxy.age = -5;  // Throws an error: Age must be a non-negative number

Enter fullscreen mode Exit fullscreen mode

Data Binding

Proxies can be used for data binding in frameworks, allowing automatic updates to the UI when the underlying data changes.


const data = {
       text: 'Hello, world!'
};

const handler = {
       set: function(target, property, value, receiver) {
         target\[property] = value;
         document.getElementById('output').textContent = value;
         return true;
       }
};

const proxy = new Proxy(data, handler);
document.getElementById('input').addEventListener('input', function(event) {
       proxy.text = event.target.value;
});
Enter fullscreen mode Exit fullscreen mode

‘Currently, I am working on a reactive javascript library focused on speed & efficiency. There I’ve used proxies at great extinct for data binding…I really don’t appreciate the phrase data binding it’s good to tell it reactive data binding.’

Tracing

Proxies can help trace property access and modifications, useful for debugging and profiling. It has a great usage in application testing.

const profile = {
       name: 'John Doe',
       email: 'john@example.com'
};

const handler = {
       get: function(target, property, receiver) {
         console.log(\`Property ${property} accessed\`);
         return target\[property];
       },
       set: function(target, property, value, receiver) {
         console.log(\`Property ${property} set to ${value}\`);
         target\[property] = value;
         return true;
       }
};

const proxy = new Proxy(profile, handler);
proxy.name;       // Logs: Property name accessed
proxy.email = 'john.doe@example.com';  // Logs: Property email set to john.doe@example.com
Enter fullscreen mode Exit fullscreen mode

‘In my reactive framework, I’ve implemented some robust test cases and while testing props of dom elements, I’ve used this for tracking any updates in props, which helped me to reflect any changes in props to the dom elements’

Immutable Objects

Proxies are also used to create immutable objects, where properties cannot be changed once they are set. It’s like object properties but constant type, can’t be changed in any meaning.

const target = {
       name: 'Immutable Object'
};
const handler = {
       set: function(target, property, value, receiver) {
         console.log(\`Cannot set property ${property} to ${value}. Object is immutable.\`);
         return false; // indicate failure to set property
       }
};

const proxy = new Proxy(target, handler);
proxy.name = 'New Name'; // Logs: Cannot set property name to New Name. Object is immutable.
console.log(proxy.name); // Logs: Immutable Object
Enter fullscreen mode Exit fullscreen mode

Meta Programming

Meta Programming means to program at the meta-level of JavaScript. JavaScript proxies unlock this meta layer/level of programming to help programmers define custom behaviors. I will publish a very much detailed Post later on about meta programming. But, if you wanna learn about this meta layer of JavaScript check the resources section!

Resources

To further deepen your understanding of JavaScript Proxies and their various applications, here are some valuable resources including meta programming!:

Conclusion

JavaScript Proxies are a powerful feature that allows developers to intercept and redefine fundamental operations on objects. They can be used for a wide range of applications, from validation and data binding to tracing and debugging. My favorite one is data binding. But, other real-world use cases have made it one of the most powerful features ever lived in the JavaScript Ecosystem. By understanding and utilizing Proxies, you can add a new level of dynamism and flexibility to your JavaScript code.

Experiment with Proxies in your projects and discover how they can help to establish greater control over your code. Happy coding!

Top comments (2)

Collapse
 
der_gopher profile image
Alex Pliutau

What could be the real world example? The code is quite complex for just accessing a map attributes.

Collapse
 
devishraq profile image
Ishraq Tanvir

There are actually several examples you can consider like Form Validation or sanitizing api response or reactive ui updates!
Consider this example on reactive ui updates

function createReactiveUI(data, updateUI) {
  return new Proxy(data, {
    set(target, property, value) {
      target[property] = value;
      updateUI(target);
      return true;
    }
  });
}
const updateUI = (data) => {
  document.getElementById('name').textContent = data.name;
  document.getElementById('age').textContent = data.age;
};

const person = createReactiveUI({ name: "Alice", age: 30 }, updateUI);

person.name = "Bob";  
person.age = 31;  
Enter fullscreen mode Exit fullscreen mode

here the ui would be update on state change in this case name & age change...this has been an perfect example how big frameworks like react or svelte handles ui updates based state change....yes, their implementation is very much robust, they may not using proxies but the ideology is much same!

And You're right that using a Proxy might seem complex for just accessing map attributes. However, the power of Proxies lies in their ability to add sophisticated behavior to seemingly simple operations. In the reactive UI example above, we're not just accessing properties, but automatically triggering UI updates whenever those properties/state change. This allows us to create a reactive system with clean, centralized logic.
Without Proxies, we might need to manually call an update function every time we change a property, or use a more complex observable pattern. The Proxy allows us to abstract this behavior, keeping our state management code cleaner and more maintainable.
Thanks for your observation!