Reflect API and Proxy Patterns
The Reflect API provides methods that mirror Proxy traps and perform default operations.
Reflect API
There is a Reflect method for each Proxy trap. Each one performs the default operation:
const obj = { x: 1 };
// Property read
console.log(obj.x); // 1
console.log(Reflect.get(obj, "x")); // 1
// Property write
obj.y = 2;
Reflect.set(obj, "z", 3);
// Property delete
delete obj.y;
Reflect.deleteProperty(obj, "z");
Using Reflect in Proxy Traps
Inside a trap, a Reflect method forwards the operation to the target:
const handler = {
get(target, property, receiver) {
console.log(`Getting ${property}`);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log(`Setting ${property} = ${value}`);
return Reflect.set(target, property, value, receiver);
},
};
const proxy = new Proxy({ x: 1 }, handler);
proxy.x; // Logs: "Getting x"
proxy.y = 2; // Logs: "Setting y = 2"
Why Use Reflect?
- Correct receiver handling: Maintains proper
thisbinding - Clear status for mutations: Methods like
Reflect.set()andReflect.deleteProperty()return booleans - Consistency: Same API as Proxy traps
Some Reflect methods can still throw, for example when the target is not a valid object, so it is not true that Reflect never throws.
// Reflect.set returns boolean instead of throwing
const frozen = Object.freeze({ x: 1 });
// Throws in strict mode:
// frozen.x = 2;
// Returns false:
console.log(Reflect.set(frozen, "x", 2)); // false
Practical Examples
Validation Proxy
function createValidatedObject(schema) {
return new Proxy(
{},
{
set(target, property, value) {
const validator = schema[property];
if (validator && !validator(value)) {
throw new TypeError(`Invalid value for ${property}`);
}
return Reflect.set(target, property, value);
},
},
);
}
const user = createValidatedObject({
name: (v) => typeof v === "string" && v.length > 0,
age: (v) => typeof v === "number" && v >= 0,
email: (v) => typeof v === "string" && v.includes("@"),
});
user.name = "Alice"; // OK
user.age = 30; // OK
user.email = "a@b.com"; // OK
// user.age = -5; // TypeError
// user.email = 'invalid'; // TypeError
Logging Proxy
function withLogging(obj, name = "Object") {
return new Proxy(obj, {
get(target, property, receiver) {
const value = Reflect.get(target, property, receiver);
console.log(`${name}.${property} -> ${value}`);
return value;
},
set(target, property, value, receiver) {
console.log(`${name}.${property} = ${value}`);
return Reflect.set(target, property, value, receiver);
},
});
}
const config = withLogging({ debug: false }, "config");
config.debug; // Logs: "config.debug -> false"
config.debug = true; // Logs: "config.debug = true"
Negative Array Indices
function negativeArray(arr) {
return new Proxy(arr, {
get(target, property, receiver) {
if (typeof property === "string") {
const index = Number(property);
if (Number.isInteger(index) && index < 0) {
const normalizedIndex = target.length + index;
return Reflect.get(target, String(normalizedIndex), receiver);
}
}
return Reflect.get(target, property, receiver);
},
});
}
const arr = negativeArray([1, 2, 3, 4, 5]);
console.log(arr[-1]); // 5 (last element)
console.log(arr[-2]); // 4 (second to last)