When working with objects, we can create a proxy object that intercepts and changes the behavior of an existing object.

We do so using the Proxy native object, introduced in ES2015.

Suppose we have a car object:

const car = {
  color: 'blue'
}

A very simple example we can make is to return a ‘Not found’ string when we try to access a property that does not exist.

You can define a proxy that is called whenever you try to access a property of this object.

You do so by creating another object that has a get() method, which receives the target object and the property as parameters:

const car = {
  color: 'blue'
}

const handler = {
  get(target, property) {
    return target[property] ?? 'Not found'
  }
}

Now we can initialize our proxy object by calling new Proxy(), passing the original object, and our handler:

const proxyObject = new Proxy(car, handler)

Now try accessing a property contained in the car object, but referencing it from proxyObject:

proxyObject.color //'blue'

This is just like calling car.color.

But when you try to access a property that does not exist on car, like car.test, you’d get back undefined. Using the proxy, you will get back the 'Not found' string, since that’s what we told it to do.

proxyObject.test //'Not found'

We’re not limited to the get() method in a proxy handler. That was just the simplest example we could write.

We have other methods we can use:

  • apply is called when we use apply() on the object
  • construct is called when we access the object constructor
  • deleteProperty is executed when we try to delete a property
  • defineProperty is called when we define a new property on the object
  • set is executed when we try to set a property

and so on. Basically we can create a guarded gate that controls everything that happens on an object, and provide additional rules and controls to implement our own logic.

Other methods (also called traps) we can use are:

  • enumerate
  • getOwnPropertyDescriptor
  • getPrototypeOf
  • has
  • isExtensible
  • ownKeys
  • preventExtensions
  • setPrototypeOf

all corresponding to the respective functionality.

You can read more about each of those on MDN.

Let’s make another example using deleteProperty. We want to prevent deleting properties of an object:

const car = {
  color: 'blue'
}

const handler = {
  deleteProperty(target, property) {
    return false
  }
}

const proxyObject = new Proxy(car, handler)

If we call delete proxyObject.color, we’ll get a TypeError:

TypeError: 'deleteProperty' on proxy: trap returned falsish for property 'color'

Of course one could always delete the property directly on the car object, but if you write your logic so that that object is inaccessible and you only expose the proxy, that is a way to encapsulate your logic.

Download my free JavaScript Beginner's Handbook, and check out my premium React/Vue/Svelte/Node/Next.js courses!