JavaScript Proxy and Reflect
Proxy and Reflect are advanced ES6 features that allow interception and customization of fundamental JavaScript operations — such as reading a property, writing a value, calling a function, or checking if a key exists. They are powerful tools for validation, logging, data binding, and building reactive frameworks.
What is a Proxy?
A Proxy wraps an object and intercepts operations performed on it. Every time code tries to read, write, delete, or call something on the proxied object, a trap (handler function) can be triggered.
Real-life analogy: A Proxy is like a secretary. When someone wants to talk to a manager (target object), they go through the secretary (proxy) first. The secretary can allow, deny, or modify the request.
Syntax:
let proxy = new Proxy(target, handler);target— the original object being wrappedhandler— an object whose methods (traps) intercept operations
Basic Proxy with get Trap
The get trap intercepts property access.
let user = { name: "Kavita", age: 28 };
let proxy = new Proxy(user, {
get(target, property) {
console.log(`Getting property: ${property}`);
return property in target ? target[property] : "Property not found";
}
});
console.log(proxy.name); // Getting property: name → Kavita
console.log(proxy.age); // Getting property: age → 28
console.log(proxy.email); // Getting property: email → Property not foundset Trap — Intercept Property Assignment
The set trap intercepts when a property value is being written. This is great for validation.
let person = {};
let proxy = new Proxy(person, {
set(target, property, value) {
if (property === "age") {
if (typeof value !== "number") {
throw new TypeError("Age must be a number.");
}
if (value < 0 || value > 120) {
throw new RangeError("Age must be between 0 and 120.");
}
}
target[property] = value;
return true; // Must return true to indicate success
}
});
proxy.name = "Suresh";
proxy.age = 30; // Passes validation
console.log(person); // { name: "Suresh", age: 30 }
// proxy.age = -5; // RangeError: Age must be between 0 and 120.
// proxy.age = "thirty"; // TypeError: Age must be a number.has Trap — Intercept the in Operator
let range = { min: 1, max: 100 };
let rangeProxy = new Proxy(range, {
has(target, key) {
// Instead of checking for a property, check if a number is in range
let num = Number(key);
return num >= target.min && num <= target.max;
}
});
console.log(50 in rangeProxy); // true (50 is in range 1–100)
console.log(150 in rangeProxy); // false (150 is outside range)deleteProperty Trap
let config = { theme: "dark", lang: "en", version: "2.0" };
let safeConfig = new Proxy(config, {
deleteProperty(target, property) {
if (property === "version") {
throw new Error("Cannot delete 'version' — it is protected.");
}
delete target[property];
return true;
}
});
delete safeConfig.lang; // Works fine
// delete safeConfig.version; // Error: Cannot delete 'version'
console.log(config); // { theme: "dark", version: "2.0" }apply Trap — Intercept Function Calls
function add(a, b) {
return a + b;
}
let trackedAdd = new Proxy(add, {
apply(target, thisArg, args) {
console.log(`add(${args.join(", ")}) called`);
let result = target.apply(thisArg, args);
console.log(`Result: ${result}`);
return result;
}
});
trackedAdd(3, 7);
// add(3, 7) called
// Result: 10Revocable Proxy
A revocable proxy can be deactivated. Once revoked, any access to the proxy throws an error.
let { proxy, revoke } = Proxy.revocable({ name: "Tanvi" }, {});
console.log(proxy.name); // Tanvi
revoke(); // Deactivate the proxy
// proxy.name; // TypeError: Cannot perform 'get' on a proxy that has been revokedWhat is Reflect?
The Reflect object provides static methods that mirror the default behavior of proxy traps. It is often used inside proxy handlers to perform the default operation while adding custom logic.
Reflect Methods Mirror Proxy Traps
| Proxy Trap | Reflect Equivalent |
|---|---|
| get | Reflect.get(target, prop) |
| set | Reflect.set(target, prop, value) |
| deleteProperty | Reflect.deleteProperty(target, prop) |
| has | Reflect.has(target, prop) |
| apply | Reflect.apply(fn, thisArg, args) |
| construct | Reflect.construct(target, args) |
Using Reflect in Proxy Traps
let target = { score: 80 };
let proxy = new Proxy(target, {
get(target, property, receiver) {
console.log(`Reading: ${property}`);
return Reflect.get(target, property, receiver); // Default behavior
},
set(target, property, value, receiver) {
console.log(`Writing: ${property} = ${value}`);
return Reflect.set(target, property, value, receiver); // Default behavior
}
});
proxy.score; // Reading: score
proxy.score = 95; // Writing: score = 95
console.log(target.score); // 95Practical Example — Observable Object
A Proxy can notify listeners whenever a property changes — this is the foundation of reactive data systems used in frameworks like Vue.js.
function makeObservable(obj, onChange) {
return new Proxy(obj, {
set(target, property, value) {
let oldValue = target[property];
target[property] = value;
onChange(property, oldValue, value);
return true;
}
});
}
let settings = makeObservable({ theme: "light", fontSize: 14 }, (key, oldVal, newVal) => {
console.log(`"${key}" changed from "${oldVal}" to "${newVal}"`);
});
settings.theme = "dark"; // "theme" changed from "light" to "dark"
settings.fontSize = 18; // "fontSize" changed from "14" to "18"Key Points to Remember
- A
Proxywraps an object and intercepts operations via trap functions defined in a handler - The
gettrap intercepts property reads; thesettrap intercepts property writes - The
applytrap intercepts function calls; thehastrap intercepts theinoperator - Always return
truefrom asettrap to indicate a successful assignment - Revocable proxies can be deactivated to cut off access to the underlying object
Reflectprovides methods that mirror each proxy trap's default behavior- Using
Reflectinside proxy handlers ensures correct default behavior while adding custom logic - Proxies are the foundation of reactivity systems in modern frameworks
