JavaScript: Proxy and Reflect APIs
Introduced in ES6, the Proxy and Reflect APIs unlocked true metaprogramming in JavaScript. They allow developers to redefine fundamental language operations (like reading a property, setting a value, or calling a function).
If you've ever used Vue 3 or MobX, you've relied entirely on Proxies under the hood.
What are Proxies and Reflect in JavaScript, and how are they used in modern frameworks?
1. The Proxy Object
A Proxy wraps an object (called the target) and allows you to intercept and customize fundamental operations performed on that object using a configuration object (called the handler). The functions inside the handler that intercept these operations are called traps.
Creating a Proxy
Let's create a Proxy that intercepts property reads (the get trap) and writes (the set trap).
const targetUser = {
name: "Alice",
age: 25
};
const handler = {
// Intercept reading a property
get(target, property) {
console.log(`Reading property: ${property}`);
if (property in target) {
return target[property];
}
return `Property ${property} does not exist!`;
},
// Intercept writing to a property
set(target, property, value) {
console.log(`Setting ${property} to ${value}`);
if (property === "age" && typeof value !== "number") {
throw new TypeError("Age must be a number!");
}
target[property] = value;
return true; // Indicate success
}
};
const proxyUser = new Proxy(targetUser, handler);
// Triggers the 'get' trap
console.log(proxyUser.name);
// Logs: "Reading property: name"
// Output: "Alice"
// Triggers the 'set' trap
proxyUser.age = 26;
// Logs: "Setting age to 26"
// Triggers validation logic in 'set' trap
// proxyUser.age = "twenty-six"; // TypeError: Age must be a number!2. The Reflect API
While you can mutate the target directly inside a Proxy trap (e.g., target[property] = value), it is considered an anti-pattern.
JavaScript introduced the Reflect object alongside Proxy. Reflect is a built-in object that provides methods for interceptable JavaScript operations. The methods on Reflect perfectly mirror the traps on a Proxy handler.
Why use Reflect?
- Clean syntax: It simplifies the default forwarding of operations.
- Boolean returns:
Reflect.set()returnstrueif successful, whereas strict-mode direct assignment might throw errors. - Proper
thisbinding: When dealing with inherited objects and getters/setters,Reflectensures thethiscontext points to the Proxy, not the original target.
Refactoring with Reflect
const handler = {
get(target, property, receiver) {
console.log(`Intercepted GET for ${property}`);
// Use Reflect to forward the operation perfectly
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`Intercepted SET for ${property}`);
return Reflect.set(target, property, value, receiver);
}
};3. Real-World Use Case: Reactivity (Vue 3)
Modern frameworks use Proxies to track state dependencies and trigger UI updates automatically.
When you render a component, the framework intercepts the get trap to record which properties the UI depends on. When you mutate the state, the set trap is triggered, notifying the framework to re-render precisely the components that rely on that property.
// A simplified reactivity system
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 1. Record dependency (e.g., UI component rendering this key)
trackDependency(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
// 2. Notify dependencies (e.g., Trigger UI re-render)
triggerUpdate(target, key);
return result;
}
});
}Senior-Level Interview Answer
The
ProxyAPI enables metaprogramming in JavaScript by allowing developers to wrap an object and define custom behavior for fundamental language operations via trap functions. Common use cases include validation, logging, data-binding, and observability. TheReflectAPI was introduced in tandem to provide static methods that correspond one-to-one with Proxy traps. UsingReflectinside Proxy traps guarantees that the default language behavior is executed safely, standardizes return values, and crucially, preserves the correctthisbinding (the receiver) when getters or setters are invoked through the prototype chain. Proxies form the architectural foundation for reactivity systems in modern frameworks like Vue 3 and state libraries like MobX.
Common Interview Mistakes
❌ Modifying the target object instead of returning Reflect
Junior developers often write target[prop] = value; return true; inside a set trap. While this works for simple objects, it breaks entirely when the object has getters/setters that rely on the this context of inherited prototypes. A senior developer always uses return Reflect.set(target, prop, value, receiver);.
❌ Using Proxy for deep observation implicitly
A Proxy only intercepts operations on the object it directly wraps. If the target object contains nested objects, modifying properties on the nested object will not trigger the top-level Proxy traps. To create a deeply reactive object, you must recursively wrap nested objects in Proxies as they are accessed.
