Proxy Pattern

Intercept and control interactions to target objects

Overview

The Proxy Pattern uses a Proxy to intercept and control interactions with target objects.

Let's say that we have a person object. We can access properties with either dot or bracket notation:

and modify property values in a similar fashion:

With the Proxy pattern, we don't want to interact with this object directly. Instead, a Proxy object intercepts the request and (optionally) forwards this to the target object - the person object in this case.


Implementation

In JavaScript, we can easily create a new proxy by using the built-in Proxy object.

Proxy Pattern

The Proxy object receives two arguments:

  1. The target object
  2. A handler object, which we can use to add functionality to the proxy. This object comes with some built-in functions that we can use, such as get and set.

The get method on the handler object gets invoked when we want to access a property, and the set method gets invoked when we want to modify a property.

proxy-implementation.js
const person = {
  name: "John Doe",
  age: 42,
  email: "john@doe.com",
  country: "Canada",
};

const personProxy = new Proxy(person, {
  get: (target, prop) => {
    console.log(`The value of ${prop} is ${target[prop]}`);
    return target[prop];
  },
  set: (target, prop, value) => {
    console.log(`Changed ${prop} from ${target[prop]} to ${value}`);
    target[prop] = value;
    return true;
  },
});

The Proxy object receives two arguments:

  1. The target object
  2. A handler object, which we can use to add functionality to the proxy. This object comes with some built-in functions that we can use, such as get and set. The get method on the handler object gets invoked when we want to access a property, and the set method gets invoked when we want to modify a property.
proxy-implementation.js
const person = {
  name: "John Doe",
  age: 42,
  email: "john@doe.com",
  country: "Canada",
};

const personProxy = new Proxy(person, {
  get: (target, prop) => {
    console.log(`The value of ${prop} is ${target[prop]}`);
    return target[prop];
  },
  set: (target, prop, value) => {
    console.log(`Changed ${prop} from ${target[prop]} to ${value}`);
    target[prop] = value;
    return true;
  },
});

Reflect

The built-in Reflect object makes it easier to manipulate the target object.

Instead of accessing properties through obj[prop] or setting properties through obj[prop] = value, we can access or modify properties on the target object through Reflect.get() and Reflect.set(). The methods receive the same arguments as the methods on the handler object.

Tradeoffs

Exercise

Challenge

Add the following validation to the user object:

  • The username property has to be a string that only contains of letters, and is at least 3 characters long
  • The email property has to be a valid email address.
  • The age property has to be a number, and has to be at least 18
  • When a property is retrieved, change the output to ${new Date()} | The value of ${property}} is ${target[property]}. For example if we get user.name, it needs to log 2022-05-31T15:29:15.303Z | The value of name is John

Solution