Proxy Pattern
The proxy pattern provides a substitute that controls access to another object. It can intercept and customize operations like get, set, and delete.
JavaScript Proxy - native implementation
ts
const handler: ProxyHandler<Record<string, unknown>> = {
get(target, key) {
console.log(`Getting: ${String(key)}`);
return Reflect.get(target, key);
},
set(target, key, value) {
console.log(`Setting: ${String(key)} = ${value}`);
return Reflect.set(target, key, value);
},
};
const obj = new Proxy({}, handler);
obj.name = 'Alice'; // logs: Setting: name = Alice
obj.name; // logs: Getting: nameValidation proxy
ts
function createValidatedUser(data: Partial<User>) {
return new Proxy(data, {
set(target, key, value) {
if (key === 'age' && (typeof value !== 'number' || value < 0)) {
throw new Error('Age must be a positive number');
}
if (key === 'email' && !value.includes('@')) {
throw new Error('Invalid email');
}
return Reflect.set(target, key, value);
},
});
}Reactive state with Proxy (how Vue 3 works)
ts
function reactive<T extends object>(target: T): T {
return new Proxy(target, {
get(obj, key) {
track(obj, key); // record dependency
return Reflect.get(obj, key);
},
set(obj, key, value) {
const result = Reflect.set(obj, key, value);
trigger(obj, key); // notify subscribers
return result;
},
});
}
const state = reactive({ count: 0 });
state.count++; // triggers re-render automaticallyAPI proxy for caching
ts
const cachedApi = new Proxy(api, {
get(target, method: string) {
return async (...args: unknown[]) => {
const key = `${method}:${JSON.stringify(args)}`;
if (cache.has(key)) return cache.get(key);
const result = await target[method](...args);
cache.set(key, result);
return result;
};
},
});Vue 3's entire reactivity system is built on
Proxy. Immer also uses proxies to allow seemingly mutable code that produces immutable updates. Understanding Proxy helps you understand how modern frameworks work under the hood.
updated
did you know it?