Proxy and Reflect in JavaScript
Introduced in ES6, Proxy and Reflect are powerful metaprogramming features in JavaScript. They allow you to intercept and customize fundamental operations on objects, such as property lookup, assignment, enumeration, and function invocation.
The Proxy Object
A Proxy object wraps another object (the target) and intercepts operations performed on it using a handler object. The handler contains methods called traps.
Syntax
const proxy = new Proxy(target, handler);
target: The original object you want to proxy.handler: An object that defines which operations will be intercepted and how to redefine them.
Basic Example: Intercepting get and set
const targetUser = {
name: "Alice",
age: 25
};
const handler = {
get(target, property) {
console.log(`Reading property: ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Writing property: ${property} with value ${value}`);
target[property] = value;
return true; // Indicates success
}
};
const userProxy = new Proxy(targetUser, handler);
console.log(userProxy.name);
// Logs: Reading property: name
// Outputs: "Alice"
userProxy.age = 26;
// Logs: Writing property: age with value 26
Common Traps
get(target, prop, receiver): Intercepts property reading.set(target, prop, value, receiver): Intercepts property writing.has(target, prop): Intercepts theinoperator.deleteProperty(target, prop): Intercepts thedeleteoperator.apply(target, thisArg, argumentsList): Intercepts function calls.construct(target, argumentsList, newTarget): Intercepts thenewoperator.
Example: Validation using set
Proxies are excellent for data validation.
const validator = {
set(target, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Age must be an integer');
}
if (value < 0) {
throw new RangeError('Age cannot be negative');
}
}
target[prop] = value;
return true;
}
};
const person = new Proxy({}, validator);
person.age = 30; // Works fine
// person.age = 'thirty'; // Throws TypeError
// person.age = -5; // Throws RangeError
The Reflect API
The Reflect object provides built-in methods that have the same names as the Proxy traps. Reflect methods map to the default, internal engine operations.
Why use Reflect?
When you intercept an operation in a Proxy, you often want to perform the original, default behavior after doing some custom logic. While you can do target[prop] = value, using Reflect is cleaner, safer, and handles edge cases (like getters/setters and the receiver context) correctly.
const robustHandler = {
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
// Safely execute the default get operation
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`Setting ${prop} to ${value}`);
// Safely execute the default set operation
return Reflect.set(target, prop, value, receiver);
}
};
const data = new Proxy({ a: 1 }, robustHandler);
data.a = 2; // Setting a to 2
console.log(data.a); // Getting a \n 2
Use Cases for Proxy & Reflect
- Reactivity Systems: Frameworks like Vue 3 and MobX use Proxies to track when properties are read (to register dependencies) and when they are set (to trigger UI re-renders).
- Data Validation: Ensuring objects only contain valid data types and values.
- Mocking and API interception: Intercepting network requests or mocking objects in testing.
- Logging and Profiling: Silently wrapping objects to log property accesses and method executions to measure performance.
- Negative Array Indices: Creating arrays that allow
arr[-1]to get the last element.
// Negative Index Array Example
const createNegativeArray = (arr) => {
return new Proxy(arr, {
get(target, prop, receiver) {
let index = Number(prop);
if (index < 0) {
prop = String(target.length + index);
}
return Reflect.get(target, prop, receiver);
}
});
};
const nums = createNegativeArray([10, 20, 30]);
console.log(nums[-1]); // 30