Proxy and Reflect
Intercepting and customizing object operations
Challenge
Section titled “Challenge”You need to validate object property assignments, intercept method calls, and implement object immutability patterns. How do you customize how objects behave?
Explanation
Section titled “Explanation”Proxy objects intercept and customize operations on objects. Reflect provides static methods for introspection and object manipulation.
Proxy Basics
Section titled “Proxy Basics”// Create a proxyconst target = { name: 'Alice', age: 30 };
const handler = { get(target, prop) { console.log(`Getting ${prop}`); return target[prop]; },
set(target, prop, value) { console.log(`Setting ${prop} = ${value}`); target[prop] = value; return true; // Indicates success }};
const proxy = new Proxy(target, handler);
console.log(proxy.name); // Getting nameproxy.age = 31; // Setting age = 31Solution: Validation Proxy
Section titled “Solution: Validation Proxy”function createValidatedObject(obj, schema) { return new Proxy(obj, { set(target, prop, value) { if (schema[prop]) { const { type, validate } = schema[prop];
if (typeof value !== type) { throw new TypeError( `${prop} must be ${type}, got ${typeof value}` ); }
if (validate && !validate(value)) { throw new Error(`Invalid value for ${prop}: ${value}`); } }
target[prop] = value; return true; } });}
// Usageconst userSchema = { name: { type: 'string', validate: (v) => v.length > 0 }, age: { type: 'number', validate: (v) => v >= 0 && v <= 150 }, email: { type: 'string', validate: (v) => v.includes('@') }};
const user = createValidatedObject({}, userSchema);
user.name = 'John'; // OKuser.age = 30; // OK// user.age = -5; // Error// user.email = 'invalid'; // ErrorReflect API
Section titled “Reflect API”// Reflect methods mirror Proxy trapsconst obj = { name: 'Bob', age: 25 };
// Get propertyconsole.log(Reflect.get(obj, 'name')); // 'Bob'
// Set propertyReflect.set(obj, 'name', 'Alice');
// Check propertyconsole.log(Reflect.has(obj, 'name')); // true
// Delete propertyReflect.deleteProperty(obj, 'age');
// Get own property namesconsole.log(Reflect.ownKeys(obj)); // ['name']
// Get property descriptorconsole.log(Reflect.getOwnPropertyDescriptor(obj, 'name'));
// Apply functionfunction greet(greeting) { return `${greeting}, ${this.name}`;}const result = Reflect.apply(greet, obj, ['Hello']);Advanced Proxy Patterns
Section titled “Advanced Proxy Patterns”// Logging proxyfunction createLoggedProxy(obj) { return new Proxy(obj, { get(target, prop) { console.log(`Accessing: ${String(prop)}`); return target[prop]; },
set(target, prop, value) { console.log(`Modifying: ${String(prop)} = ${value}`); target[prop] = value; return true; } });}
// Negative index access (like Python)function createArrayProxy(arr) { return new Proxy(arr, { get(target, prop) { let index = Number(prop); if (index < 0) { index = target.length + index; } return target[index]; } });}
const arr = [1, 2, 3, 4, 5];const negativeArr = createArrayProxy(arr);console.log(negativeArr[-1]); // 5 (last element)console.log(negativeArr[-2]); // 4 (second to last)
// Observable patternfunction createObservable(obj, callback) { return new Proxy(obj, { set(target, prop, value) { if (target[prop] !== value) { target[prop] = value; callback(prop, value); } return true; } });}
const user = createObservable({ name: 'John' }, (prop, value) => { console.log(`${prop} changed to ${value}`);});
user.name = 'Jane'; // Logs: name changed to JanePerformance Considerations
Section titled “Performance Considerations”// ⚠️ Proxy has performance overheadconst target = { x: 1 };const handler = { get(t, p) { return t[p]; }};const proxy = new Proxy(target, handler);
// Accessing via proxy is slower than direct accessconsole.time('direct');for (let i = 0; i < 1000000; i++) { target.x;}console.timeEnd('direct'); // ~0.5ms
console.time('proxy');for (let i = 0; i < 1000000; i++) { proxy.x;}console.timeEnd('proxy'); // ~10ms (slower)
// Use proxies for:// - Validation and security// - Observation and logging// - API design (not performance-critical)