JS: Proxy
(new in ECMAScript 2015)
What is a proxy
Proxy is a feature in JavaScript that is like a wrapper to objects. It lets you create a new object, based on a existing target object, and the new object behaves like the target object, except you can change any of its behavior, such as getting property value, setting property value, delete property, function call (when the target object is a function), constructor behavior, object extensibility, etc.
// the target object const my_obj = { name: "Alice" }; // create a handler. // handler is a special object syntax, to change the target object behavior. // here, we change the property access behavior const handler = { get(xtarget, xprop) { console.log(`Getting ${xprop}`); return xtarget[xprop]; }, }; // create a proxy const proxy = new Proxy(my_obj, handler); console.log(proxy.name); // Getting name // Alice
Basic syntax
new Proxy(target, handler)
target: Any object, array, function, or even another proxy.handler: An object with special syntax of methods that intercept operations. Known as traps
List of traps
- getPrototypeOf()
- setPrototypeOf()
- isExtensible()
- preventExtensions()
- getOwnPropertyDescriptor()
- defineProperty()
- has()
- get()
- set()
- deleteProperty()
- ownKeys()
for function objects
- apply()
- construct()
Example. get method
get(target, property, receiver) {body}
traps property access (e.g. obj.prop).
the receiver is the this-binding that a getter property function would receive. Normally the value is same as target.
// example. proxy with get method const xobj = { pp: 30 }; const new_obj = new Proxy(xobj, { get(aa, bb, cc) { return [aa, bb, cc]; }, }); console.log(new_obj.pp); // [ { pp: 30 }, "pp", { pp: 30 } ]
trap property assignment
set(target, property, value, receiver) {body}
traps property assignment.
the receiver is the this-binding that a setter property function would receive. Normally the value is same as target.
// example. proxy with set method const xobj = {}; const xnew = new Proxy(xobj, { set(xtarget, xprop, xvalue) { console.log(xtarget, xprop, xvalue); if (xvalue > 0) { // must return true on success return true; } else { return false; } }, }); xnew.aa = 3; // prints // {} aa 3 // xnew.bb = -3; // error: Uncaught (in promise) TypeError: 'set' on proxy: trap returned falsish for property 'bb'
trap checking property existance
has(target, property)
Intercepts the in operator.
const xobj = { _password: "12345" }; const newobj = new Proxy(xobj, { has(xtarget, xprop) { if (xprop.startsWith("_")) return false; // hide private props return xprop in xtarget; }, }); console.log("_password" in newobj); // false console.log("toString" in newobj); // true (inherited)
(ai answer)
3. most useful traps
deleteProperty(target, property)
const xdata = { dog: 5, cat: 3, bird: 4 }; // prevent deletion of dog const xnew_data = new Proxy(xdata, { deleteProperty(target, prop) { if (prop === "dog") { console.log("dog hit"); // return false; } delete target[prop]; // return true; }, }); delete xnew_data.cat; // delete xnew_data.dog; // error: Uncaught (in promise) TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'cat'
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.first_name = name; } } const LoggedUser = new Proxy(User, { construct(xtarget, args) { console.log(`Creating new ${xtarget.name} with`, args); return new xtarget(...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.