Lesson 18 · 18 of 20
Proxy and Reflect · JavaScript
Intercept reads, writes and deletes in JavaScript with Proxy, and let Reflect do the default behaviour, using a validated user object.

A `Proxy` wraps an object and lets you intercept the operations performed on it: reading, assigning, deleting, checking with in and more. Each intercepted operation is a trap in the handler. validated returns a proxy around { id: 7 } with three traps: set, get and deleteProperty.
The set trap runs on every assignment, such as user.age = 34. It looks for a rule for that property and, if the value breaks it, throws a TypeError: so user.age = 15 is rejected without touching the object. If the value is valid, it logs the change and performs the real assignment.
`Reflect` exposes the language's normal operations as functions: Reflect.set, Reflect.get, Reflect.deleteProperty, Reflect.has and so on. Inside a trap, calling them is the right way to do the default thing after adding your own logic, and they return true or false just as the proxy expects.
The get trap returns a default text, (no phone), for properties that do not exist, and deleteProperty protects the id. Operations without a trap, such as Reflect.ownKeys or the in operator, go straight through to the original object. This technique powers validators, change logs and the reactivity of some UI libraries.
Code · main.js
// A Proxy wraps an object and intercepts operations through "traps".function validated(target, rules) { return new Proxy(target, { set(obj, key, value, receiver) { if (rules[key] && !rules[key](value)) { throw new TypeError(`invalid ${key}: ${JSON.stringify(value)}`); } console.log(`set ${key} = ${JSON.stringify(value)}`); return Reflect.set(obj, key, value, receiver); // the normal assignment }, get(obj, key, receiver) { return key in obj ? Reflect.get(obj, key, receiver) : `(no ${String(key)})`; }, deleteProperty(obj, key) { if (key === "id") throw new Error("id cannot be deleted"); return Reflect.deleteProperty(obj, key); }, });}const user = validated({ id: 7 }, { email: (v) => typeof v === "string" && v.includes("@"), age: (v) => Number.isInteger(v) && v >= 18,});user.email = "ana@shop.es";user.age = 34;for (const attempt of [() => (user.age = 15), () => delete user.id]) { try { attempt(); } catch (err) { console.log(`${err.name}: ${err.message}`); }}console.log(user.email, user.age, user.phone);console.log("Reflect.ownKeys:", Reflect.ownKeys(user).join(", "));console.log("Has email?", Reflect.has(user, "email"), "| has phone?", "phone" in user);Output · node main.js
set email = "ana@shop.es" set age = 34 TypeError: invalid age: 15 Error: id cannot be deleted ana@shop.es 34 (no phone) Reflect.ownKeys: id, email, age Has email? true | has phone? false
Key points
- A
Proxyintercepts operations on an object through traps. set,getanddeletePropertyare common traps.Reflectperforms the default operation and pairs naturally with each trap.- Operations without a trap reach the original object unchanged.
Exercise
Add a has trap that hides properties starting with _ from the in operator, and make email impossible to change once it has been set.