Skip to content
Plurafy

Proxy and Reflect

Intercepting and customizing object operations

You need to validate object property assignments, intercept method calls, and implement object immutability patterns. How do you customize how objects behave?

Proxy objects intercept and customize operations on objects. Reflect provides static methods for introspection and object manipulation.

// Create a proxy
const 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 name
proxy.age = 31; // Setting age = 31
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;
}
});
}
// Usage
const 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'; // OK
user.age = 30; // OK
// user.age = -5; // Error
// user.email = 'invalid'; // Error
// Reflect methods mirror Proxy traps
const obj = { name: 'Bob', age: 25 };
// Get property
console.log(Reflect.get(obj, 'name')); // 'Bob'
// Set property
Reflect.set(obj, 'name', 'Alice');
// Check property
console.log(Reflect.has(obj, 'name')); // true
// Delete property
Reflect.deleteProperty(obj, 'age');
// Get own property names
console.log(Reflect.ownKeys(obj)); // ['name']
// Get property descriptor
console.log(Reflect.getOwnPropertyDescriptor(obj, 'name'));
// Apply function
function greet(greeting) {
return `${greeting}, ${this.name}`;
}
const result = Reflect.apply(greet, obj, ['Hello']);
// Logging proxy
function 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 pattern
function 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 Jane
// ⚠️ Proxy has performance overhead
const target = { x: 1 };
const handler = {
get(t, p) { return t[p]; }
};
const proxy = new Proxy(target, handler);
// Accessing via proxy is slower than direct access
console.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)