Proxy and Reflect
Proxy and Reflect are ES6 features for metaprogramming, which means writing code that manipulates other code. A Proxy lets you intercept and customize the fundamental operations on an object. Reflect gives you methods that perform those same operations.
Proxy Basics
A Proxy wraps an object and intercepts operations through “traps”:
const target = { name: "Alice", age: 30 };
const handler = {
get(target, property) {
console.log(`Getting ${property}`);
return target[property];
},
};
const proxy = new Proxy(target, handler);
console.log(proxy.name);
// Logs: "Getting name"
// Returns: "Alice"
Common Traps
| Trap | Intercepts |
|---|---|
get |
Reading a property |
set |
Writing a property |
has |
The in operator |
deleteProperty |
The delete operator |
apply |
Function calls |
construct |
The new operator |
getPrototypeOf |
Object.getPrototypeOf() |
setPrototypeOf |
Object.setPrototypeOf() |
ownKeys |
Object.keys(), Reflect.ownKeys() |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor() |
defineProperty |
Object.defineProperty() |
Intercepting Property Reads with get
const handler = {
get(target, property, receiver) {
if (property in target) {
return Reflect.get(target, property, receiver);
}
return `Property "${property}" not found`;
},
};
const proxy = new Proxy({ a: 1 }, handler);
console.log(proxy.a); // 1
console.log(proxy.b); // "Property "b" not found"
Intercepting Property Writes with set
const handler = {
set(target, property, value, receiver) {
if (property === "age" && typeof value !== "number") {
throw new TypeError("Age must be a number");
}
if (property === "age" && value < 0) {
throw new RangeError("Age must be positive");
}
return Reflect.set(target, property, value, receiver);
},
};
const person = new Proxy({}, handler);
person.name = "Alice"; // OK
person.age = 30; // OK
// person.age = 'thirty'; // TypeError
// person.age = -5; // RangeError
Intercepting the in Operator with has
const handler = {
has(target, property) {
if (typeof property === "string" && property.startsWith("_")) {
return false; // Hide private properties
}
return Reflect.has(target, property);
},
};
const obj = new Proxy({ _secret: "hidden", visible: "shown" }, handler);
console.log("visible" in obj); // true
console.log("_secret" in obj); // false
Intercepting Deletion with deleteProperty
const handler = {
deleteProperty(target, property) {
if (typeof property === "string" && property.startsWith("_")) {
throw new Error("Cannot delete private properties");
}
return Reflect.deleteProperty(target, property);
},
};
const obj = new Proxy({ _id: 1, name: "test" }, handler);
delete obj.name; // OK
// delete obj._id; // Error
Intercepting Function Calls with apply
function sum(a, b) {
return a + b;
}
const handler = {
apply(target, thisArg, args) {
console.log(`Called with args: ${args}`);
return Reflect.apply(target, thisArg, args);
},
};
const proxy = new Proxy(sum, handler);
console.log(proxy(1, 2));
// Logs: "Called with args: 1,2"
// Returns: 3
Intercepting Construction with construct
class Person {
constructor(name) {
this.name = name;
}
}
const handler = {
construct(target, args, newTarget) {
console.log(`Creating instance with: ${args}`);
return Reflect.construct(target, args, newTarget);
},
};
const ProxiedPerson = new Proxy(Person, handler);
const p = new ProxiedPerson("Alice");
// Logs: "Creating instance with: Alice"