Proxy & Reflect
Intercept Every Operation on an Object
LinkedIn Hook
What if you could sit between every property read, every property write, every
delete, everyincheck — and decide what happens?That is what a
Proxydoes. It wraps an object and installs "traps" that fire on fundamental operations. Vue.js 3's reactivity. MobX observables. Negative array indexing. Validation schemas. Immutable wrappers. All built on the same 13-trap API.And
Reflectis the twin you call from inside those traps — the built-in object that gives you the default behavior for each operation. Instead ofreturn target[prop](which quietly breaks prototype chains), you returnReflect.get(target, prop, receiver)and everything just works.Most developers never touch Proxy. Library authors never stop touching it. If you have ever wondered how frameworks know a property changed without you calling a setter — this lesson is the answer.
Read the full lesson -> [link]
#JavaScript #InterviewPrep #Proxy #Reflect #ES6 #Metaprogramming #AdvancedJS
What You'll Learn
- What a Proxy is and the full list of 13 traps it can install
- How Reflect mirrors every trap and why you should call it inside handlers
- Real-world patterns: validation, reactive state, negative indexing, logging, revocable access
The Security Checkpoint Analogy
A Proxy is like a security checkpoint at a building entrance. Every person (operation) trying to enter or leave must pass through the checkpoint, where guards (traps) can inspect, modify, or deny access. Reflect is the rule book that tells guards the "normal" behavior for each operation.
Proxy Basics
const target = { name: "Rakibul", age: 25 };
const handler = {
get(target, property, receiver) {
console.log(`Reading '${property}'`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`Setting '${property}' to ${value}`);
return Reflect.set(target, property, value, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.name; // Logs: Reading 'name' -> "Rakibul"
proxy.age = 26; // Logs: Setting 'age' to 26
Validation Proxy
function createValidated(schema) {
return new Proxy({}, {
set(target, property, value) {
const validator = schema[property];
if (!validator) {
throw new Error(`Unknown property: ${property}`);
}
if (!validator(value)) {
throw new TypeError(`Invalid value for ${property}: ${value}`);
}
return Reflect.set(target, property, value);
}
});
}
const user = createValidated({
name: v => typeof v === "string" && v.length > 0,
age: v => typeof v === "number" && v >= 0 && v <= 150,
email: v => typeof v === "string" && v.includes("@")
});
user.name = "Rakibul"; // works
user.age = 25; // works
// user.age = -5; // TypeError: Invalid value for age: -5
// user.foo = "bar"; // Error: Unknown property: foo
Negative Array Index with Proxy
function negativeArray(arr) {
return new Proxy(arr, {
get(target, property, receiver) {
const index = Number(property);
if (Number.isInteger(index) && index < 0) {
return target[target.length + index];
}
return Reflect.get(target, property, receiver);
}
});
}
const arr = negativeArray([10, 20, 30, 40, 50]);
arr[0]; // 10
arr[-1]; // 50 (last element)
arr[-2]; // 40 (second to last)
arr.length; // 5 (non-numeric properties still work)
Reactive Data Binding (Vue.js 3 Pattern)
function reactive(obj, onChange) {
return new Proxy(obj, {
set(target, property, value, receiver) {
const oldValue = target[property];
const result = Reflect.set(target, property, value, receiver);
if (oldValue !== value) {
onChange(property, value, oldValue);
}
return result;
},
deleteProperty(target, property) {
const oldValue = target[property];
const result = Reflect.deleteProperty(target, property);
onChange(property, undefined, oldValue);
return result;
}
});
}
const state = reactive({ count: 0, name: "Rakibul" }, (prop, newVal, oldVal) => {
console.log(`${prop} changed: ${oldVal} -> ${newVal}`);
// In a real framework, this would trigger a re-render
});
state.count = 1; // "count changed: 0 -> 1"
state.name = "Karim"; // "name changed: Rakibul -> Karim"
delete state.name; // "name changed: Karim -> undefined"
Logging Proxy
function withLogging(obj) {
return new Proxy(obj, {
get(target, property, receiver) {
console.log(`[GET] ${String(property)}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`[SET] ${String(property)} = ${JSON.stringify(value)}`);
return Reflect.set(target, property, value, receiver);
},
has(target, property) {
console.log(`[HAS] ${String(property)}`);
return Reflect.has(target, property);
},
deleteProperty(target, property) {
console.log(`[DELETE] ${String(property)}`);
return Reflect.deleteProperty(target, property);
}
});
}
const obj = withLogging({ x: 1, y: 2 });
obj.x; // [GET] x -> 1
obj.z = 3; // [SET] z = 3
"x" in obj; // [HAS] x -> true
delete obj.y; // [DELETE] y
Proxy Traps — Complete List
const handler = {
// Property access
get(target, property, receiver) {},
set(target, property, value, receiver) {},
has(target, property) {}, // 'in' operator
deleteProperty(target, property) {}, // 'delete' operator
// Function call
apply(target, thisArg, args) {}, // function call
construct(target, args, newTarget) {}, // 'new' operator
// Property definition
getOwnPropertyDescriptor(target, property) {},
defineProperty(target, property, descriptor) {},
// Prototype
getPrototypeOf(target) {},
setPrototypeOf(target, prototype) {},
// Enumeration
ownKeys(target) {}, // Object.keys, for...in
// Extensibility
isExtensible(target) {},
preventExtensions(target) {}
};
Reflect — The Mirror of Proxy
Every Proxy trap has a corresponding Reflect method. Reflect provides the default behavior for each operation:
// Reflect.get — like obj[property]
Reflect.get({ a: 1 }, "a"); // 1
// Reflect.set — like obj[property] = value
const obj = {};
Reflect.set(obj, "a", 1); // true, obj.a === 1
// Reflect.has — like property in obj
Reflect.has({ a: 1 }, "a"); // true
// Reflect.deleteProperty — like delete obj[property]
Reflect.deleteProperty({ a: 1 }, "a"); // true
// Reflect.apply — like fn.apply(thisArg, args)
Reflect.apply(Math.max, null, [1, 2, 3]); // 3
// Reflect.construct — like new Constructor(...args)
Reflect.construct(Date, [2025, 0, 1]); // Date object
// Why use Reflect over direct operations?
// 1. Returns boolean instead of throwing (set, defineProperty)
// 2. Consistent functional interface
// 3. Proper forwarding in Proxy traps (preserves receiver)
Revocable Proxies
// Create a proxy that can be permanently disabled
const { proxy, revoke } = Proxy.revocable({ secret: "data" }, {
get(target, property) {
return Reflect.get(target, property);
}
});
proxy.secret; // "data"
revoke(); // Permanently disable the proxy
// proxy.secret; // TypeError: Cannot perform 'get' on a proxy that has been revoked
// Useful for: temporary access tokens, permission revocation, API timeouts
Common Mistakes
- Returning
target[property]directly inside agettrap instead ofReflect.get(target, property, receiver)— breaks the prototype chain because the receiver is not forwarded. Always use Reflect with the receiver argument when forwarding. - Forgetting that
setanddeletePropertytraps must return a boolean — returningundefinedin strict mode throws a TypeError. Alwaysreturn Reflect.set(...)or explicittrue. - Proxying
Date,Map, orSetwith onlyget/settraps — these built-ins store internal slots and do not call traps for their native methods. You must explicitly handle method calls viaapplyor bind them to the target.
Interview Questions
Q: What is a Proxy and how does it work?
A Proxy wraps a target object and intercepts fundamental operations (get, set, has, delete, etc.) via handler functions called "traps." The Proxy constructor takes a target and a handler object. Each trap receives the target and operation details, and can modify, validate, or log the operation before forwarding it.
Q: What is Reflect and why is it used with Proxy?
Reflect is a built-in object providing methods that mirror every Proxy trap. Using
Reflect.get(),Reflect.set(), etc., inside traps ensures you perform the default operation correctly — especially important for maintaining thereceiver(for proper prototype chain behavior). Reflect methods also return booleans instead of throwing on failure.
Q: Give 3 practical use cases for Proxy.
- Validation — intercept
setto validate values before storing. 2) Reactive data — interceptsetto trigger UI re-renders when data changes (Vue.js 3 uses this). 3) Negative array indexing — interceptgetto supportarr[-1]for last element access. Others: logging, access control, lazy loading, default values.
Q: What is a revocable proxy?
Proxy.revocable(target, handler)returns{ proxy, revoke }. Callingrevoke()permanently disables the proxy — any operation on it throws a TypeError. Useful for temporary access, permission windows, and secure API designs where access can be revoked.
Q: Name 4 Proxy traps.
get(property read),set(property write),has(inoperator),deleteProperty(deleteoperator). Others includeapply(function call),construct(new),ownKeys(Object.keys/for...in), andgetPrototypeOf.
Q: How does Vue.js 3 use Proxy for reactivity?
Vue 3 wraps each reactive object in a Proxy. The
gettrap registers which effect (component render) accessed which property. Thesettrap then re-runs every effect that registered for that property. This replaces Vue 2'sObject.definePropertyapproach, which could not track newly added properties or array index assignments.
Q: How would you implement negative array indexing?
Wrap the array in a Proxy with a
gettrap: if the property is a negative integer, returntarget[target.length + index]; otherwise forward viaReflect.get. See thenegativeArrayexample above.
Quick Reference — Cheat Sheet
PROXY & REFLECT
new Proxy(target, handler) -> wrap target with traps
Proxy.revocable(target, handler) -> { proxy, revoke }
HANDLER TRAPS (13 total)
get, set, has, deleteProperty -> property access
apply, construct -> function/new
ownKeys -> enumeration
getPrototypeOf, setPrototypeOf -> prototype
getOwnPropertyDescriptor -> inspection
defineProperty -> definition
isExtensible, preventExtensions -> extensibility
REFLECT — MIRROR OF PROXY
Reflect.get / set / has / deleteProperty
Reflect.apply / construct
Reflect.ownKeys / getPrototypeOf / setPrototypeOf
Always forward `receiver` when calling Reflect from a trap
WHY REFLECT OVER DIRECT OPS
1. Returns boolean instead of throwing
2. Consistent functional interface
3. Preserves prototype chain via receiver
USE CASES
Validation | Reactive state (Vue 3) | Logging | Negative indexing
Lazy loading | Default values | Access control | API revocation
Previous: Lesson 9.7 — Set, Map, WeakSet, WeakMap Next: Lesson 10.1 — try...catch...finally
This is Lesson 9.8 of the JavaScript Interview Prep Course — 14 chapters, 87 lessons.