JavaScript Proxies allow you to intercept core functionality on JavaScript Objects so you can tweak the behaviour of existing features. And it is not limited to objects created by you but is also possible with objects created by others. As a reminder, in JavaScript there are eight different data types: Boolean, Null, Undefined, Number, BigInt, String, Symbol and Object (see MDN). So changing the behaviour of objects lets us change a huge part of JavaScript.

A proxy looks like this:

Here we change the get function to return 37 each time a variable is not defined. First the function is created, which executes each time a property is accessed. The parameters are the object itself and the accessed property name as a string. If the property name is available the corresponding value and otherwise the default value is returned. The values set to the proxy are transparently set to the original object. A proxy cannot store values, all values are set and received from the original value. The proxy can only transform the values before setting it to the original object after receiving it from the original object.

To understand proxies you have to know these terms:

  • The target is the original object which is wrapped by the proxy.
  • Proxy is the new object which wraps the original object and shows the new behaviour.
  • Trap is the function which is executed before the original functionality is processed and defines the new behaviour.
  • Handler is the object with all traps inside.

List of Traps

The following list shows all the possibilities of traps. Implementing them is optional.

handler.get()
A trap for getting property values
handler.set()
A trap for setting property values
handler.deleteProperty()
A trap for the delete operator.
handler.apply()
A trap for a function call
handler.construct()
A trap for the new operator
handler.has()
A trap for the in operator
handler.ownKeys()
A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols
handler.getPrototypeOf()
A trap for Object.getPrototypeOf
handler.setPrototypeOf()
A trap for Object.setPrototypeOf
handler.isExtensible()
A trap for Object.isExtensible
handler.preventExtensions()
A trap for Object.preventExtensions
handler.getOwnPropertyDescriptor()
A trap for Object.getOwnPropertyDescriptor.
handler.defineProperty()
A trap for Object.defineProperty

Copied from MDN

Examples

The following examples will show you what is possible with JavaScript Proxies.

Something Is Changing Your Object (Set Trap)

When something is changing an object, a set trap becomes handy to identify the code spot which forces the change. Therefore, instead of returning the original object, the proxy object is returned. The proxy defines a trap for setting a property and will log the trace where the property has changed.

The return true at the end of the set trap indicates that the assignment succeeded.

Negative Array Indices (Get and Set Trap)

Negative Array indices mean counting from the end of an array instead of from the beginning. Like supported by the Array.splice method.

API Generation On The Fly (Get Trap)

In Spring Boot you can generate functions to query the database with hibernate by only creating the function declaration. This is also possible with Proxies and it feels like calling an implemented function. Here the get trap is used.

The disadvantages are that during compile time, the compiler doesn’t know which functions exist later and neither knows about the returned objects. Therefore it is hard for it to help with typings.

Revocable Proxies

Proxies can not only change the behaviour of an object, they can also control the access to the object. The function Proxy.revocable  returns two objects, first the proxy itself and second the revoke function.

The return value is the only difference between a Proxy.revocable  and the new Proxy. It is still possible to add traps to the Proxy.

When the revoke function is called, the original object is not accessible anymore, it will be be garbage-collected. The revoke function can be called multiple times without effect.

This can be handy if the object should have controlled access only. When, as alternative, an object is set to undefined, only the pointer is removed and the object still exists.Therefore the object can be accessed by other variables which have the Object assigned.

Libraries Using JavaScript Proxies

Until here I only showed small examples to understand the use of proxies. Now, to show you their full potential, let’s have a look at two libraries which make use of JavaScript Proxies.

Tpyo

The tpyo library makes use of the get Proxy and allows to call a method on an object even if the method call includes a typo. So for example the following is possible:

This is possible because when a property is accessed the trap checks if the property is available. If it is, the value is returned.
If the property is not available on the object, the trap reads all properties and returns the one with the lowest Levenshtein distance. See implementation here.

This maybe doesn’t make the most sense but shows the power of Proxies very well.

Immerjs

Immerjs is a JavaScript Library which produces immutable data structures by using copy on write. The syntax is similar to how normally objects are altered. The change of the object is wrapped in a function called ‘produce’, which returns the new object.

This is possible with the heavy use of proxies which you can see here.

This library shows the power of proxies, how they can alter the usage of objects without changing the syntax by much.

Summary

JavaScript Proxies are a useful and powerful tool. It can completely change the way objects behave and is not restricted to objects you have created yourself. The use of JavaScript proxies is easy to understand and implement. However, due to their ability to change the core functionality of objects, it can create odd behaviour when not expected.

Use cases where you need proxies may be restricted but when you need to use them it is good to know they exist.

JavaScript Proxies can be used in each major browser except Internet Explorer 11 (see caniuse).

Sources