JS: Proxy
2016-11-08 xtodo this page is work in progress
(new in ECMAScript 2015)
proxy is like a wrapper to js objects. It lets you change the behavior of things that act on a object.
Proxy object is used to
create new proxy like this:
var = new Proxy(target , handler)
// object var tt = {x:3}; // we want to change the behavior of accessing properties of tt // the “handler” var hh = { get: function(tt, propertyName, receiver) { return 99; } }; // create proxy object. var pr = new Proxy ( tt, hh ); // this will target tt, with handler hh // access a property console.log (pr.x); // prints 99 console.log (pr.y); // prints 99
ECMAScript 2015 §Reflection#sec-proxy-constructor
(AI generated)
JavaScript Proxy Tutorial: Complete Guide
JavaScript Proxy is one of the most powerful and underused features introduced in ES6 (2015). It lets you create a proxy object that intercepts and customizes fundamental operations on a target object (getting properties, setting them, calling functions, etc.).
1. What is a Proxy?
A Proxy wraps a target object and uses traps (handler functions) to control behavior when operations are performed on it.
const target = { name: "Alice" }; const handler = { get(target, prop) { console.log(`Getting ${prop}`); return target[prop]; } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Output: "Getting name" followed by "Alice"
The proxy looks and behaves like the target, but you can intercept every interaction.
2. Basic Syntax
const proxy = new Proxy(target, handler);
target: Any object, array, function, or even another proxy.handler: An object whose properties are traps (methods that intercept operations).
3. Most Useful Traps
Here are the most commonly used traps with examples:
get(target, property, receiver)
Intercepts property access (obj.prop or obj['prop']).
const person = { age: 30 }; const loggedPerson = new Proxy(person, { get(target, prop) { if (prop in target) { console.log(`Accessed ${prop}`); return target[prop]; } return `No such property: ${prop}`; } }); console.log(loggedPerson.age); // Accessed age → 30 console.log(loggedPerson.name); // Accessed name → "No such property: name"
set(target, property, value, receiver)
Intercepts property assignment.
const user = {}; const validatedUser = new Proxy(user, { set(target, prop, value) { if (prop === 'age' && (typeof value !== 'number' || value < 0)) { throw new Error('Age must be a positive number'); } target[prop] = value; console.log(`Set ${prop} = ${value}`); return true; // must return true on success } }); validatedUser.name = "Bob"; // Set name = Bob validatedUser.age = 25; // Set age = 25 // validatedUser.age = -5; // throws Error
has(target, property)
Intercepts the in operator.
const secret = { _password: "12345" }; const safeSecret = new Proxy(secret, { has(target, prop) { if (prop.startsWith('_')) return false; // hide private props return prop in target; } }); console.log('_password' in safeSecret); // false console.log('toString' in safeSecret); // true (inherited)
deleteProperty(target, property)
const data = { temp: "secret" }; const protectedData = new Proxy(data, { deleteProperty(target, prop) { if (prop === 'temp') { console.warn("Cannot delete protected property"); return false; } delete target[prop]; return true; } });
apply(target, thisArg, argumentsList) – For functions
function add(a, b) { return a + b; } const loggedAdd = new Proxy(add, { apply(target, thisArg, args) { console.log(`Called with ${args}`); return target.apply(thisArg, args); } }); console.log(loggedAdd(3, 4)); // Called with 3,4 → 7
construct(target, argumentsList, newTarget) – For new
class User { constructor(name) { this.name = name; } } const LoggedUser = new Proxy(User, { construct(target, args) { console.log(`Creating new ${target.name} with`, args); return new target(...args); } }); const u = new LoggedUser("Alice");
4. Other Notable Traps
ownKeys(target)→Object.keys(),for...in,Object.getOwnPropertyNames()getOwnPropertyDescriptor(target, prop)defineProperty(target, prop, descriptor)getPrototypeOf/setPrototypeOfisExtensible/preventExtensions
5. Practical Examples
1. Negative Array Indexing (like Python)
function createNegativeArray(arr) { return new Proxy(arr, { get(target, prop) { const index = Number(prop); if (index < 0) { return target[target.length + index]; } return target[prop]; } }); } const fruits = createNegativeArray(["apple", "banana", "cherry"]); console.log(fruits[-1]); // "cherry" console.log(fruits[-2]); // "banana"
2. Observable / Reactive (simple version)
function makeObservable(target) { const observers = new Set(); return new Proxy(target, { set(obj, prop, value) { const result = Reflect.set(obj, prop, value); observers.forEach(observer => observer({ prop, value })); return result; }, subscribe(callback) { observers.add(callback); return () => observers.delete(callback); } }); } const state = makeObservable({ count: 0 }); state.subscribe(({ prop, value }) => console.log(`${prop} changed to ${value}`)); state.count = 42; // triggers callback
3. Default Values / Fallbacks
const config = new Proxy({}, { get(target, prop) { return prop in target ? target[prop] : `default_${prop}`; } }); console.log(config.theme); // "default_theme" config.theme = "dark"; console.log(config.theme); // "dark"
4. Memoization / Caching
function memoize(fn) { const cache = new Map(); return new Proxy(fn, { apply(target, thisArg, args) { const key = JSON.stringify(args); if (!cache.has(key)) { cache.set(key, target.apply(thisArg, args)); } return cache.get(key); } }); } const expensive = memoize((x) => { console.log("Computing..."); return x * x; }); console.log(expensive(5)); // Computing... → 25 console.log(expensive(5)); // 25 (cached)
6. Best Practices & Gotchas
-
Use
Reflectmethods inside traps when possible:get(target, prop, receiver) { return Reflect.get(target, prop, receiver); // preserves .this correctly } -
Performance: Proxies have overhead. Don't use them on hot paths without benchmarking.
-
Revocable Proxies (useful for security/sandboxing):
const { proxy, revoke } = Proxy.revocable(target, handler); // ... later revoke(); // proxy becomes unusable -
Proxies are not transparent:
proxy instanceof TargetConstructormay fail.Object.getPrototypeOf(proxy)returns the target's prototype.- Many libraries break with proxies (e.g., some deep-clone functions).
-
Proxy of a Proxy works but can get complicated.
7. Real-World Use Cases
- Data validation / sanitization
- Logging and debugging
- ORM / data binding (Vue 3, MobX, etc.)
- API mocking
- Private properties (before
#syntax) - Domain-specific languages / fluent APIs
- Sandboxing untrusted code
- Lazy loading / virtual properties
8. Browser/Node Support
Proxy is supported in all modern environments (Node.js since v6, all evergreen browsers).
Exercise for you:
Try implementing a readonly proxy that throws on any set or deleteProperty attempt.
Would you like me to expand any section (e.g., more advanced traps, TypeScript examples, comparison with Object.defineProperty, or a full reactive system example)?