DMarlon Studio

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.

JavaScript · Proxy and Reflect in DMarlon Studio: the example's code and its output in the terminal

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 Proxy intercepts operations on an object through traps.
  • set, get and deleteProperty are common traps.
  • Reflect performs 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.